diff --git a/.circleci/config.yml b/.circleci/config.yml index 08faefd24f71b..b05cdbd2ca316 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2.1 parameters: ci_builder_image: type: string - default: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:v0.53.0 + default: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:v0.55.0 ci_builder_rust_image: type: string default: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder-rust:latest @@ -26,6 +26,9 @@ parameters: reproducibility_dispatch: type: boolean default: false + diff_asterisc_bytecode_dispatch: + type: boolean + default: false kontrol_dispatch: type: boolean default: false @@ -41,12 +44,19 @@ parameters: publish_contract_artifacts_dispatch: type: boolean default: false + stale_check_dispatch: + type: boolean + default: false + contracts_coverage_dispatch: + type: boolean + default: false orbs: go: circleci/go@1.8.0 gcp-cli: circleci/gcp-cli@3.0.1 slack: circleci/slack@4.10.1 shellcheck: circleci/shellcheck@3.2.0 + codecov: codecov/codecov@5.0.3 commands: gcp-oidc-authenticate: description: "Authenticate with GCP using a CircleCI OIDC token." @@ -113,7 +123,11 @@ commands: description: "Install the dependencies for the smart contracts" steps: - run: - command: just install + name: Install dependencies + command: | + # Manually craft the submodule update command in order to take advantage + # of the -j parameter, which speeds it up a lot. + git submodule update --init --recursive --force -j 8 working_directory: packages/contracts-bedrock notify-failures-on-develop: @@ -152,8 +166,8 @@ commands: jobs: cannon-go-lint-and-test: - docker: - - image: <> + machine: true + resource_class: swc/ax101 parameters: skip_slow_tests: type: boolean @@ -162,22 +176,20 @@ jobs: description: Whether to notify on failure type: boolean default: false - mips64: - type: boolean - default: false - resource_class: xlarge + mips_word_size: + type: integer + default: 32 steps: - checkout - check-changed: patterns: cannon,packages/contracts-bedrock/src/cannon,op-preimage,go.mod - attach_workspace: at: "." - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - run: name: prep Cannon results dir - command: mkdir -p /tmp/test-results + command: | + mkdir -p ./tmp/test-results + mkdir -p ./tmp/testlogs - run: name: build Cannon example binaries command: make elf # only compile ELF binaries with Go, we do not have MIPS GCC for creating the debug-dumps. @@ -189,34 +201,38 @@ jobs: working_directory: cannon - when: condition: - not: <> + equal: [32, <>] steps: - run: name: Cannon Go 32-bit tests command: | export SKIP_SLOW_TESTS=<> - mkdir -p /testlogs - gotestsum --format=testname --junitfile=/tmp/test-results/cannon.xml --jsonfile=/testlogs/log.json \ - -- -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./... + gotestsum --format=testname --junitfile=../tmp/test-results/cannon-32.xml --jsonfile=../tmp/testlogs/log-32.json \ + -- -parallel=$(nproc) -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage-32.out ./... working_directory: cannon + - codecov/upload: + disable_search: true + files: ./cannon/coverage-32.out + flags: cannon-go-tests-32 - when: - condition: <> + condition: + equal: [64, <>] steps: - run: name: Cannon Go 64-bit tests command: | export SKIP_SLOW_TESTS=<> - mkdir -p /testlogs - gotestsum --format=testname --junitfile=/tmp/test-results/cannon.xml --jsonfile=/testlogs/log.json \ - -- --tags=cannon64 -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./... + gotestsum --format=testname --junitfile=../tmp/test-results/cannon-64.xml --jsonfile=../tmp/testlogs/log-64.json \ + -- --tags=cannon64 -parallel=$(nproc) -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage-64.out ./... working_directory: cannon - - run: - name: upload Cannon coverage - command: codecov --verbose --clean --flags cannon-go-tests + - codecov/upload: + disable_search: true + files: ./cannon/coverage-64.out + flags: cannon-go-tests-64 - store_test_results: - path: /tmp/test-results + path: ./tmp/test-results - store_artifacts: - path: /testlogs + path: ./tmp/testlogs when: always - when: condition: <> @@ -237,28 +253,61 @@ jobs: command: python3 maketests.py && git diff --exit-code working_directory: cannon/mipsevm/tests/open_mips_tests - contracts-bedrock-build: + diff-asterisc-bytecode: docker: - image: <> - resource_class: xlarge + resource_class: medium + steps: + - checkout + - run: + name: Check `RISCV.sol` bytecode + working_directory: packages/contracts-bedrock + command: | + # Clone asterisc @ the pinned version to fetch remote `RISCV.sol` + ASTERISC_REV="v$(yq '.tools.asterisc' ../../mise.toml)" + REMOTE_ASTERISC_PATH="./src/vendor/asterisc/RISCV_Remote.sol" + git clone https://github.com/ethereum-optimism/asterisc \ + -b $ASTERISC_REV && \ + cp ./asterisc/rvsol/src/RISCV.sol $REMOTE_ASTERISC_PATH + + # Replace import paths + sed -i -e 's/@optimism\///' $REMOTE_ASTERISC_PATH + # Replace legacy interface paths + sed -i -e 's/src\/cannon\/interfaces\//interfaces\/cannon\//g' $REMOTE_ASTERISC_PATH + sed -i -e 's/src\/dispute\/interfaces\//interfaces\/dispute\//g' $REMOTE_ASTERISC_PATH + # Replace contract name + sed -i -e 's/contract RISCV/contract RISCV_Remote/' $REMOTE_ASTERISC_PATH + + # Install deps + forge install + + # Diff bytecode, with both contracts compiled in the local environment. + REMOTE_ASTERISC_CODE="$(forge inspect RISCV_Remote bytecode | tr -d '\n')" + LOCAL_ASTERISC_CODE="$(forge inspect RISCV bytecode | tr -d '\n')" + if [ "$REMOTE_ASTERISC_CODE" != "$LOCAL_ASTERISC_CODE" ]; then + echo "Asterisc bytecode mismatch. Local version does not match remote. Diff:" + diff <(echo "$REMOTE_ASTERISC_CODE") <(echo "$LOCAL_ASTERISC_CODE") + else + echo "Asterisc version up to date." + fi + - notify-failures-on-develop: + mentions: "@clabby @proofs-team" + + contracts-bedrock-build: + machine: true + resource_class: swc/ax101 parameters: - skip_pattern: - description: Glob pattern of tests to skip + build_args: + description: Forge build arguments type: string default: "" + profile: + description: Profile to use for building + type: string + default: ci steps: - checkout - install-contracts-dependencies - - restore_cache: - name: Restore Go modules cache - keys: - - gomod-contracts-build-{{ checksum "go.sum" }} - - gomod-contracts-build- - - restore_cache: - name: Restore Go build cache - keys: - - golang-build-cache-contracts-build-{{ checksum "go.sum" }} - - golang-build-cache-contracts-build- - run: name: Print forge version command: forge --version @@ -268,24 +317,14 @@ jobs: working_directory: packages/contracts-bedrock - run: name: Build contracts - command: forge build --deny-warnings --skip <> + command: forge build <> environment: - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: <> working_directory: packages/contracts-bedrock - run: name: Generate allocs command: | make devnet-allocs - - save_cache: - name: Save Go modules cache - key: gomod-contracts-build-{{ checksum "go.sum" }} - paths: - - "/go/pkg/mod" - - save_cache: - name: Save Go build cache - key: golang-build-cache-contracts-build-{{ checksum "go.sum" }} - paths: - - "/root/.cache/go-build" - persist_to_workspace: root: "." paths: @@ -366,9 +405,11 @@ jobs: machine: image: <> resource_class: "<>" - docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages + docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages steps: - checkout + - run: + command: git submodule update --init - attach_workspace: at: /tmp/docker_images - run: @@ -477,7 +518,7 @@ jobs: docker save -o /tmp/docker_images/<>.tar $IMAGE_NAME - persist_to_workspace: root: /tmp/docker_images - paths: # only write the one file, to avoid concurrent workspace-file additions + paths: # only write the one file, to avoid concurrent workspace-file additions - "<>.tar" - when: condition: "<>" @@ -490,18 +531,18 @@ jobs: condition: or: - and: - - "<>" - - "<>" + - "<>" + - "<>" - and: - - "<>" - - equal: [develop, << pipeline.git.branch >>] + - "<>" + - equal: [develop, << pipeline.git.branch >>] steps: - gcp-oidc-authenticate: service_account_email: GCP_SERVICE_ATTESTOR_ACCOUNT_EMAIL - run: name: Sign command: | - VER=$(jq -r .binary_signer < versions.json) + VER=$(yq '.tools.binary_signer' mise.toml) wget -O - "https://github.com/ethereum-optimism/binary_signer/archive/refs/tags/v${VER}.tar.gz" | tar xz cd "binary_signer-${VER}/signer" @@ -580,23 +621,17 @@ jobs: command: just coverage-lcov no_output_timeout: 18m environment: - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: cicoverage working_directory: packages/contracts-bedrock - - run: - name: upload coverage - command: codecov --verbose --clean --flags contracts-bedrock-tests - environment: - FOUNDRY_PROFILE: ci + - codecov/upload: + disable_search: true + files: ./packages/contracts-bedrock/lcov.info + flags: contracts-bedrock-tests contracts-bedrock-tests: - docker: - - image: <> - resource_class: xlarge + machine: true + resource_class: swc/ax101 parameters: - test_parallelism: - description: Number of test jobs to run in parallel - type: integer - default: 4 test_list: description: List of test files to run type: string @@ -620,7 +655,6 @@ jobs: description: Profile to use for testing type: string default: ci - parallelism: <> steps: - checkout - attach_workspace: { at: "." } @@ -636,14 +670,6 @@ jobs: working_directory: packages/contracts-bedrock - check-changed: patterns: contracts-bedrock,op-node - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - name: Restore Go build cache - keys: - - golang-build-cache-contracts-bedrock-tests-{{ checksum "go.sum" }} - - golang-build-cache-contracts-bedrock-tests- - run: name: Print dependencies command: just dep-status @@ -679,6 +705,10 @@ jobs: FOUNDRY_PROFILE: ci working_directory: packages/contracts-bedrock when: on_fail + - run: + name: Lint forge test names + command: just lint-forge-tests-check-no-build + working_directory: packages/contracts-bedrock - save_cache: name: Save Go build cache key: golang-build-cache-contracts-bedrock-tests-{{ checksum "go.sum" }} @@ -687,26 +717,27 @@ jobs: - notify-failures-on-develop contracts-bedrock-checks: - docker: - - image: <> - resource_class: xlarge + machine: true + resource_class: swc/ax101 steps: - checkout - attach_workspace: { at: "." } - install-contracts-dependencies - check-changed: patterns: contracts-bedrock,op-node - - setup_remote_docker: - docker_layer_caching: true - run: name: print forge version command: forge --version + - run-contracts-check: + command: check-kontrol-summaries-unchanged + - run-contracts-check: + command: semgrep-test-validity-check + - run-contracts-check: + command: semgrep - run-contracts-check: command: semver-lock - run-contracts-check: command: semver-diff-check-no-build - - run-contracts-check: - command: semver-natspec-check-no-build - run-contracts-check: command: validate-deploy-configs - run-contracts-check: @@ -715,29 +746,14 @@ jobs: command: gas-snapshot-check - run-contracts-check: command: snapshots-check-no-build - - run-contracts-check: - command: kontrol-deployment-check - run-contracts-check: command: interfaces-check-no-build - run-contracts-check: command: size-check - run-contracts-check: command: unused-imports-check-no-build - - contracts-bedrock-validate-spacers: - docker: - - image: <> - resource_class: medium - steps: - - checkout - - attach_workspace: { at: "." } - - install-contracts-dependencies - - check-changed: - patterns: contracts-bedrock - - run: - name: validate spacers - command: just validate-spacers-no-build - working_directory: packages/contracts-bedrock + - run-contracts-check: + command: validate-spacers-no-build todo-issues: parameters: @@ -768,8 +784,8 @@ jobs: description: should load in foundry artifacts type: boolean default: false - docker: - - image: <> + machine: true + resource_class: swc/ax101 steps: - checkout - check-changed: @@ -777,182 +793,24 @@ jobs: - attach_workspace: at: "." if: ${{ uses_artifacts }} - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - name: Restore Go build cache - keys: - - golang-build-cache-fuzz-golang-{{ checksum "go.sum" }} - - golang-build-cache-fuzz-golang- - run: name: Fuzz command: make fuzz working_directory: "<>" - - save_cache: - name: Save Go build cache - key: golang-build-cache-fuzz-golang-{{ checksum "go.sum" }} - paths: - - "/root/.cache/go-build" go-lint: - docker: - - image: <> + machine: true + resource_class: swc/ax101 steps: - checkout - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - name: Restore Go build cache - keys: - - golang-build-cache-lint-{{ checksum "go.sum" }} - - golang-build-cache-lint- - - restore_cache: - name: Restore Go lint cache - keys: - - golang-lint-cache-{{ checksum "go.sum" }} - - golang-lint-cache- - run: name: run Go linter command: | - # Identify how many cores it defaults to - golangci-lint run --help | grep concurrency make lint-go working_directory: . - - save_cache: - name: Save Go build cache - key: golang-build-cache-lint-{{ checksum "go.sum" }} - paths: - - "/root/.cache/go-build" - - save_cache: - name: Save Go lint cache - key: golang-lint-cache-{{ checksum "go.sum" }} - paths: - - "/root/.cache/golangci-lint" - - go-test-kurtosis: - parameters: - module: - description: Go Module Name - type: string - uses_artifacts: - description: Uses contract artifacts - type: boolean - default: false - test_directory: - description: Test directory - type: string - default: "./..." - machine: - image: <> # only used to enable codecov. - resource_class: xlarge - steps: - - run: - name: Install components - command: | - go version - go install gotest.tools/gotestsum@v1.11.0 - - run: - name: Install Kurtosis - command: | - echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list - sudo apt update - sudo apt install kurtosis-cli=1.3.0 - kurtosis engine start - - checkout - - when: - condition: <> - steps: - - attach_workspace: { at: "." } - - run: - name: prep results dir - command: | - # Make sure the workspace is properly owned - sudo chown -R $USER:$USER . - mkdir -p /tmp/test-results - mkdir -p /tmp/testlogs - - run: - name: run tests - command: | - ENABLE_KURTOSIS=true gotestsum \ - --format=testname \ - --junitfile=/tmp/test-results/<>.xml \ - --jsonfile=/tmp/testlogs/log.json \ - -- -parallel=8 \ - -coverpkg=github.com/ethereum-optimism/optimism/... \ - -coverprofile=coverage.out <> - working_directory: <> - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/testlogs - when: always - go-test: + go-tests: parameters: - module: - description: Go Module Name - type: string - uses_artifacts: - description: Uses contract artifacts - type: boolean - default: false - docker: - - image: <> - resource_class: xlarge - circleci_ip_ranges: true - steps: - - checkout - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - name: Restore Go build cache - keys: - - golang-build-cache-test-<>-{{ checksum "go.sum" }} - - golang-build-cache-test- - - when: - condition: <> - steps: - - attach_workspace: { at: "." } - - run: - name: Install components - command: | - go version - go install gotest.tools/gotestsum@v1.11.0 - - run: - name: prep results dir - command: mkdir -p /tmp/test-results && mkdir -p /tmp/testlogs - - run: - name: run tests - command: | - ENABLE_ANVIL=true SEPOLIA_RPC_URL="https://ci-sepolia-l1.optimism.io" gotestsum --format=testname --junitfile=/tmp/test-results/<>.xml --jsonfile=/tmp/testlogs/log.json \ - -- -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out ./... - working_directory: <> - - save_cache: - name: Save Go build cache - key: golang-build-cache-test-<>-{{ checksum "go.sum" }} - paths: - - "/root/.cache/go-build" - - store_test_results: - path: /tmp/test-results - - store_artifacts: - path: /tmp/testlogs - when: always - - go-e2e-test: - parameters: - module: - description: Go Module Name - type: string - target: - description: The make target to execute - type: string - parallelism: - description: Number of parallel test runs - type: integer - default: 6 notify: description: Whether to notify on failure type: boolean @@ -961,65 +819,75 @@ jobs: description: Slack user or group to mention when notifying of failures type: string default: "" - docker: - - image: <> - resource_class: xlarge - parallelism: <> + resource_class: + description: Machine resource class + type: string + default: swc/ax101 + no_output_timeout: + description: Timeout for when CircleCI kills the job if there's no output + type: string + default: 60m + test_timeout: + description: Timeout for running tests + type: string + default: 10m + environment_overrides: + description: Environment overrides + type: string + default: "" + packages: + description: List of packages to test + type: string + machine: true + resource_class: swc/ax101 steps: - checkout - - check-changed: - patterns: op-(.+),cannon,contracts-bedrock - - run: - name: prep results dir - command: mkdir -p /tmp/test-results - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - name: Restore Go build cache - keys: - - golang-build-cache-e2e-{{ checksum "go.sum" }} - - golang-build-cache-e2e- - attach_workspace: - at: /tmp/workspace - - run: - name: Load devnet-allocs and artifacts - command: | - mkdir -p .devnet - cp -r /tmp/workspace/.devnet* . - cp -r /tmp/workspace/packages/contracts-bedrock/forge-artifacts packages/contracts-bedrock/forge-artifacts - cp /tmp/workspace/packages/contracts-bedrock/deploy-config/devnetL1.json packages/contracts-bedrock/deploy-config/devnetL1.json - cp -r /tmp/workspace/packages/contracts-bedrock/deployments/devnetL1 packages/contracts-bedrock/deployments/devnetL1 + at: "." - run: - name: print go's available MIPS targets - command: go tool dist list | grep mips + name: build op-program-client + command: make op-program-client + working_directory: op-program - run: name: run tests - no_output_timeout: 20m + no_output_timeout: <> command: | - mkdir -p /testlogs + mkdir -p ./tmp/test-results && mkdir -p ./tmp/testlogs + + cd op-e2e && make pre-test && cd .. + + packages=( + <> + ) + formatted_packages="" + for package in "${packages[@]}"; do + formatted_packages="$formatted_packages ./$package/..." + done - # The below env var gets overridden when running make test-cannon, but we - # need to explicitly set it here to prevent Cannon from running when we don't - # want it to. + export ENABLE_KURTOSIS=true export OP_E2E_CANNON_ENABLED="false" - # Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional - # constraint that gotestsum does not currently (nor likely will) accept files from different packages when building. - JUNIT_FILE=/tmp/test-results/<>_<>.xml JSON_LOG_FILE=/testlogs/test.log make <> - working_directory: <> - - store_artifacts: - path: /testlogs - when: always + export OP_E2E_SKIP_SLOW_TEST=true + export OP_E2E_USE_HTTP=true + export ENABLE_ANVIL=true + export SEPOLIA_RPC_URL="https://ci-sepolia-l1-archive.optimism.io" + export MAINNET_RPC_URL="https://ci-mainnet-l1-archive.optimism.io" + + <> + + gotestsum --format=testname \ + --junitfile=./tmp/test-results/results.xml \ + --jsonfile=./tmp/testlogs/log.json \ + --rerun-fails=2 \ + --packages="$formatted_packages" \ + -- -coverprofile=coverage.out -timeout=<> + - codecov/upload: + disable_search: true + files: ./coverage.out + - store_test_results: + path: ./tmp/test-results - store_artifacts: - path: /tmp/test-results + path: ./tmp/testlogs when: always - - store_test_results: - path: /tmp/test-results - - save_cache: - name: Save Go build cache - key: golang-build-cache-e2e-{{ checksum "go.sum" }} - paths: - - "/root/.cache/go-build" - when: condition: "<>" steps: @@ -1027,18 +895,10 @@ jobs: mentions: "<>" cannon-prestate: - docker: - - image: <> + machine: true + resource_class: swc/ax101 steps: - checkout - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - name: Restore Go build cache - keys: - - golang-build-cache-cannon-prestate-{{ checksum "go.sum" }} - - golang-build-cache-cannon-prestate- - run: name: Build cannon command: make cannon @@ -1071,11 +931,6 @@ jobs: - "op-program/bin/prestate-mt.json" - "op-program/bin/meta-mt.json" - "op-program/bin/prestate-proof-mt.json" - - save_cache: - name: Save Go build cache - key: golang-build-cache-cannon-prestate-{{ checksum "go.sum" }} - paths: - - "/root/.cache/go-build" - persist_to_workspace: root: . paths: @@ -1086,63 +941,10 @@ jobs: preimage-reproducibility: docker: - image: <> - parameters: - version: - type: string steps: - checkout - setup_remote_docker - - run: - name: Switch to tag - command: | - git fetch - git checkout "op-program/v<>" - git submodule update --init --recursive - - run: - name: Set expected prestate hashes - command: | - if [[ "<>" == "0.1.0" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x038942ec840131a63c49fa514a3f0577ae401fd5584d56ad50cdf5a8b41d4538"' >> $BASH_ENV - elif [[ "<>" == "0.2.0" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808"' >> $BASH_ENV - elif [[ "<>" == "0.3.0" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x034c8cc69f22c35ae386a97136715dd48aaf97fd190942a111bfa680c2f2f421"' >> $BASH_ENV - elif [[ "<>" == "1.0.0" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x037ef3c1a487960b0e633d3e513df020c43432769f41a634d18a9595cbf53c55"' >> $BASH_ENV - elif [[ "<>" == "1.1.0" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x03e69d3de5155f4a80da99dd534561cbddd4f9dd56c9ecc704d6886625711d2b"' >> $BASH_ENV - elif [[ "<>" == "1.2.0" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x03617abec0b255dc7fc7a0513a2c2220140a1dcd7a1c8eca567659bd67e05cea"' >> $BASH_ENV - elif [[ "<>" == "1.3.0-rc.1" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x0367c4aa897bffbded0b523f277ca892298dc3c691baf37bc2099b86024f9673"' >> $BASH_ENV - elif [[ "<>" == "1.3.0-rc.2" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x0385c3f8ee78491001d92b90b07d0cf387b7b52ab9b83b4d87c994e92cf823ba"' >> $BASH_ENV - elif [[ "<>" == "1.3.0-rc.3" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x030de10d9da911a2b180ecfae2aeaba8758961fc28262ce989458c6f9a547922"' >> $BASH_ENV - elif [[ "<>" == "1.3.1-rc.1" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x03e806a2859a875267a563462a06d4d1d1b455a9efee959a46e21e54b6caf69a"' >> $BASH_ENV - elif [[ "<>" == "1.3.1-rc.2" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"' >> $BASH_ENV - elif [[ "<>" == "1.3.1" ]]; then - echo 'export EXPECTED_PRESTATE_HASH="0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"' >> $BASH_ENV - else - echo "Unknown prestate version <>" - exit 1 - fi - - run: - name: Build prestate - command: make reproducible-prestate - - run: - name: Verify prestate - command: | - ACTUAL=$(jq -r .pre ./op-program/bin/prestate-proof.json) - echo "Expected: ${EXPECTED_PRESTATE_HASH}" - echo "Actual: ${ACTUAL}" - if [[ "${EXPECTED_PRESTATE_HASH}" != "${ACTUAL}" ]] - then - echo "Prestate did not match expected" - exit 1 - fi + - run: make -C op-program verify-reproducibility - notify-failures-on-develop: mentions: "@proofs-team" @@ -1172,13 +974,13 @@ jobs: paths: - "/root/.cache/go-build" - notify-failures-on-develop: - mentions: "@proofs-squad" + mentions: "@proofs-team" semgrep-scan: parameters: diff_branch: type: string - default: develop + default: op-es scan_command: type: string default: semgrep ci --timeout=100 @@ -1194,7 +996,7 @@ jobs: - checkout - unless: condition: - equal: [ "develop", << pipeline.git.branch >> ] + equal: [ "op-es", << pipeline.git.branch >> ] steps: - run: # Scan changed files in PRs, block on new issues only (existing issues ignored) @@ -1250,7 +1052,7 @@ jobs: paths: - "/go/pkg/mod" - bedrock-go-tests: # just a helper, that depends on all the actual test jobs + bedrock-go-tests: # just a helper, that depends on all the actual test jobs docker: # Use a smaller base image to avoid pulling the huge ci-builder # image which is not needed for this job and sometimes misses @@ -1261,6 +1063,7 @@ jobs: - run: echo Done fpp-verify: + circleci_ip_ranges: true docker: - image: cimg/go:1.21 steps: @@ -1376,60 +1179,104 @@ jobs: command: bash scripts/ops/publish-artifacts.sh working_directory: packages/contracts-bedrock + go-release: + parameters: + module: + description: Go Module Name + type: string + filename: + description: Goreleaser config file + default: .goreleaser.yaml + type: string + docker: + - image: <> + resource_class: large + steps: + - setup_remote_docker + - gcp-cli/install + - gcp-oidc-authenticate: + gcp_cred_config_file_path: /root/gcp_cred_config.json + oidc_token_file_path: /root/oidc_token.json + - checkout + - run: + name: Install goreleaser pro + command: | + mkdir -p /tmp/goreleaser + cd /tmp/goreleaser + curl -L -o goreleaser.tgz https://github.com/goreleaser/goreleaser-pro/releases/download/v2.4.3-pro/goreleaser-pro_Linux_x86_64.tar.gz + tar -xzvf goreleaser.tgz + mv goreleaser /usr/local/bin/goreleaser + - run: + name: Configure Docker + command: | + gcloud auth configure-docker us-docker.pkg.dev + - run: + name: Run goreleaser + command: | + goreleaser release --clean -f ./<>/<> + + stale-check: + docker: + - image: cimg/python:3.11 + steps: + - run: + name: Run Stale Check Script + command: | + git clone --branch main --depth 1 https://github.com/ethereum-optimism/circleci-utils.git /tmp/circleci-utils + cd /tmp/circleci-utils/stale-check + pip3 install -r requirements.txt + python3 stale-check.py --repo "ethereum-optimism/${CIRCLE_PROJECT_REPONAME}" --github-token "${STALE_GITHUB_TOKEN}" + workflows: main: when: and: - or: # Trigger on new commits - - equal: [ webhook, << pipeline.trigger_source >> ] - # Trigger on manual triggers if explicitly requested - - equal: [ true, << pipeline.parameters.main_dispatch >> ] + # - equal: [ webhook, << pipeline.trigger_source >> ] + # Trigger on manual triggers if explicitly requested + - equal: [true, << pipeline.parameters.main_dispatch >>] - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] + equal: [scheduled_pipeline, << pipeline.trigger_source >>] jobs: - go-mod-download - contracts-bedrock-build: + name: contracts-bedrock-build # Build with just core + script contracts. - skip_pattern: test + build_args: --deny-warnings --skip test + - contracts-bedrock-build: + name: contracts-bedrock-build-coverage + profile: cicoverage - check-kontrol-build: requires: - contracts-bedrock-build - - contracts-bedrock-tests: - # Test everything except PreimageOracle.t.sol since it's slow. - name: contracts-bedrock-tests - test_parallelism: 4 - test_list: find test -name "*.t.sol" -not -name "PreimageOracle.t.sol" - - contracts-bedrock-tests: - # PreimageOracle test is slow, run it separately to unblock CI. - name: contracts-bedrock-tests-preimage-oracle - test_parallelism: 1 - test_list: find test -name "PreimageOracle.t.sol" - - contracts-bedrock-tests: - # Heavily fuzz any fuzz tests within added or modified test files. - name: contracts-bedrock-tests-heavy-fuzz-modified - test_parallelism: 1 - test_list: git diff origin/develop...HEAD --name-only -- './test/**/*.t.sol' | sed 's|packages/contracts-bedrock/||' - test_timeout: 1h - test_profile: ciheavy - - contracts-bedrock-coverage - - contracts-bedrock-checks: - requires: - - contracts-bedrock-build - - contracts-bedrock-validate-spacers: - requires: - - contracts-bedrock-build - - semgrep-scan + # - contracts-bedrock-tests: + # # Test everything except PreimageOracle.t.sol since it's slow. + # name: contracts-bedrock-tests + # test_list: find test -name "*.t.sol" -not -name "PreimageOracle.t.sol" + # - contracts-bedrock-tests: + # # PreimageOracle test is slow, run it separately to unblock CI. + # name: contracts-bedrock-tests-preimage-oracle + # test_list: find test -name "PreimageOracle.t.sol" + # - contracts-bedrock-tests: + # # Heavily fuzz any fuzz tests within added or modified test files. + # 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 + # - contracts-bedrock-checks: + # requires: + # - contracts-bedrock-build + # - diff-asterisc-bytecode - semgrep-scan: name: semgrep-scan-local - scan_command: semgrep scan --timeout=100 --config=./.semgrep --error . - - go-lint: - requires: - - go-mod-download + scan_command: semgrep scan --timeout=100 --config .semgrep/rules/ --error . + - semgrep-scan: + name: semgrep-test + scan_command: semgrep scan --test --config .semgrep/rules/ .semgrep/tests/ + - go-lint - fuzz-golang: name: fuzz-golang-<> - requires: - - go-mod-download on_changes: <> matrix: parameters: @@ -1438,107 +1285,60 @@ workflows: - op-node - op-service - op-chain-ops - - fuzz-golang: - name: cannon-fuzz - package_name: cannon - on_changes: cannon,packages/contracts-bedrock/src/cannon - uses_artifacts: true - requires: ["go-mod-download", "contracts-bedrock-build"] - - fuzz-golang: - name: op-e2e-fuzz - package_name: op-e2e - on_changes: op-e2e,packages/contracts-bedrock/src - uses_artifacts: true - requires: ["go-mod-download", "contracts-bedrock-build"] - - go-test: - name: <>-tests - requires: - - go-mod-download - matrix: - parameters: - module: - - op-batcher - - op-node - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - op-program - - op-service - - op-supervisor - - go-test: - name: semver-natspec-tests - module: packages/contracts-bedrock/scripts/checks/semver-natspec - - go-test: - name: op-chain-ops-tests - module: op-chain-ops - uses_artifacts: true - requires: - - go-mod-download - - contracts-bedrock-build - - go-test-kurtosis: - name: op-chain-ops-integration - module: op-chain-ops - test_directory: ./deployer/integration_test - uses_artifacts: true - requires: ["contracts-bedrock-build"] - - go-e2e-test: - name: op-e2e-HTTP-tests - module: op-e2e - target: test-http - parallelism: 8 - requires: - - go-mod-download - - contracts-bedrock-build - - go-e2e-test: - name: op-e2e-action-tests - module: op-e2e - target: test-actions - parallelism: 1 - requires: - - go-mod-download - - contracts-bedrock-build - - go-e2e-test: - name: op-e2e-fault-proof-tests - module: op-e2e - target: test-fault-proofs - parallelism: 4 + # - fuzz-golang: + # name: cannon-fuzz + # package_name: cannon + # on_changes: cannon,packages/contracts-bedrock/src/cannon + # uses_artifacts: true + # requires: ["contracts-bedrock-build"] + # - fuzz-golang: + # name: op-e2e-fuzz + # package_name: op-e2e + # on_changes: op-e2e,packages/contracts-bedrock/src + # uses_artifacts: true + # requires: ["contracts-bedrock-build"] + - go-tests: + packages: | + op-batcher + op-chain-ops + op-node + op-proposer + op-challenger + op-dispute-mon + op-conductor + op-program + op-service + op-supervisor + op-deployer + op-e2e/system + op-e2e/e2eutils + op-e2e/opgeth + op-e2e/interop + op-e2e/actions + op-e2e/faultproofs + packages/contracts-bedrock/scripts/checks requires: - contracts-bedrock-build - cannon-prestate - - op-program-compat: - requires: - - op-program-tests - - bedrock-go-tests: - requires: - - go-mod-download - - go-lint - - cannon-build-test-vectors - - cannon-go-lint-and-test - - check-generated-mocks-op-node - - check-generated-mocks-op-service - - go-mod-download - - op-batcher-tests - - op-chain-ops-tests - - op-chain-ops-integration - - op-node-tests - - op-proposer-tests - - op-challenger-tests - - op-dispute-mon-tests - - op-conductor-tests - - op-program-tests - - op-program-compat - - op-service-tests - - op-supervisor-tests - - op-e2e-HTTP-tests - - op-e2e-fault-proof-tests - - op-e2e-action-tests - # Not needed for the devnet but we want to make sure they build successfully - - cannon-docker-build - - op-dispute-mon-docker-build - - op-program-docker-build - - op-supervisor-docker-build - - proofs-tools-docker-build + # - op-program-compat + # - bedrock-go-tests: + # requires: + # - go-mod-download + # - go-lint + # - cannon-build-test-vectors + # - cannon-go-lint-and-test-32-bit + # - cannon-go-lint-and-test-64-bit + # - check-generated-mocks-op-node + # - check-generated-mocks-op-service + # - go-mod-download + # - op-program-compat + # # Not needed for the devnet but we want to make sure they build successfully + # - cannon-docker-build + # - op-dispute-mon-docker-build + # - op-program-docker-build + # - op-supervisor-docker-build + # - proofs-tools-docker-build + # - go-tests - docker-build: name: <>-docker-build docker_tags: <>,<> @@ -1556,287 +1356,299 @@ workflows: - op-conductor - da-server - op-supervisor - - op-deployer - cannon - - cannon-prestate: - requires: - - go-mod-download - - check-generated-mocks-op-node - - check-generated-mocks-op-service - - cannon-go-lint-and-test: - requires: - - contracts-bedrock-build - skip_slow_tests: true - notify: true - - cannon-build-test-vectors - - todo-issues: - name: todo-issues-check - check_closed: false - - shellcheck/check: - name: shell-check - # We don't need the `exclude` key as the orb detects the `.shellcheckrc` - dir: . - ignore-dirs: - ./packages/contracts-bedrock/lib - - release: - when: - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - # Wait for approval on the release - - hold: - type: approval - filters: - tags: - only: /^(da-server|ci-builder(-rust)?|proofs-tools|cannon|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/ - branches: - ignore: /.*/ - # Standard (medium) cross-platform docker images go here - - docker-build: - matrix: - parameters: - docker_name: - - op-node - - op-batcher - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-ufm - - op-supervisor - - op-deployer - - cannon - name: <>-docker-release - docker_tags: <> - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - filters: - tags: - only: /^<>\/v.*/ - branches: - ignore: /.*/ - context: - - oplabs-gcr-release - requires: - - hold - # Checks for cross-platform images go here - - check-cross-platform: - matrix: - parameters: - op_component: - - op-node - - op-batcher - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-ufm - - op-supervisor - - op-deployer - - cannon - name: <>-cross-platform - requires: - - op-node-docker-release - - op-batcher-docker-release - - op-proposer-docker-release - - op-challenger-docker-release - - op-dispute-mon-docker-release - - op-conductor-docker-release - - da-server-docker-release - - op-ufm-docker-release - - op-supervisor-docker-release - - op-deployer-docker-release - - cannon-docker-release - # Standard (xlarge) AMD-only docker images go here - - docker-build: - matrix: - parameters: - docker_name: - - ci-builder - - ci-builder-rust - - proofs-tools - name: <>-docker-release - resource_class: xlarge - docker_tags: <> - publish: true - release: true - filters: - tags: - only: /^<>\/v.*/ - branches: - ignore: /.*/ - context: - - oplabs-gcr-release - requires: - - hold + - cannon-prestate + # - check-generated-mocks-op-node + # - check-generated-mocks-op-service + # - cannon-go-lint-and-test: + # name: cannon-go-lint-and-test-<>-bit + # requires: + # - contracts-bedrock-build + # skip_slow_tests: true + # notify: true + # matrix: + # parameters: + # mips_word_size: [32, 64] + # - cannon-build-test-vectors + # - todo-issues: + # name: todo-issues-check + # check_closed: false + # - shellcheck/check: + # name: shell-check + # # We don't need the `exclude` key as the orb detects the `.shellcheckrc` + # dir: . + # ignore-dirs: ./packages/contracts-bedrock/lib - scheduled-todo-issues: - when: - equal: [ build_four_hours, <> ] - jobs: - - todo-issues: - name: todo-issue-checks - context: - - slack + # go-release-deployer: + # jobs: + # - go-release: + # filters: + # tags: + # only: /^op-deployer.*/ + # branches: + # ignore: /.*/ + # module: op-deployer + # context: + # - oplabs-gcr-release - scheduled-fpp: - when: - equal: [ build_hourly, <> ] - jobs: - - fpp-verify: - context: - - slack - - oplabs-fpp-nodes + # release: + # when: + # not: + # equal: [scheduled_pipeline, << pipeline.trigger_source >>] + # jobs: + # # Wait for approval on the release + # - hold: + # type: approval + # filters: + # tags: + # only: /^(da-server|ci-builder(-rust)?|proofs-tools|cannon|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/ + # branches: + # ignore: /.*/ + # # Standard (medium) cross-platform docker images go here + # - docker-build: + # matrix: + # parameters: + # docker_name: + # - op-node + # - op-batcher + # - op-proposer + # - op-challenger + # - op-dispute-mon + # - op-conductor + # - da-server + # - op-ufm + # - op-supervisor + # - op-deployer + # - cannon + # name: <>-docker-release + # docker_tags: <> + # platforms: "linux/amd64,linux/arm64" + # publish: true + # release: true + # filters: + # tags: + # only: /^<>\/v.*/ + # branches: + # ignore: /.*/ + # context: + # - oplabs-gcr-release + # requires: + # - hold + # # Checks for cross-platform images go here + # - check-cross-platform: + # matrix: + # parameters: + # op_component: + # - op-node + # - op-batcher + # - op-proposer + # - op-challenger + # - op-dispute-mon + # - op-conductor + # - da-server + # - op-ufm + # - op-supervisor + # - op-deployer + # - cannon + # name: <>-cross-platform + # requires: + # - op-node-docker-release + # - op-batcher-docker-release + # - op-proposer-docker-release + # - op-challenger-docker-release + # - op-dispute-mon-docker-release + # - op-conductor-docker-release + # - da-server-docker-release + # - op-ufm-docker-release + # - op-supervisor-docker-release + # - cannon-docker-release + # # Standard (xlarge) AMD-only docker images go here + # - docker-build: + # matrix: + # parameters: + # docker_name: + # - ci-builder + # - ci-builder-rust + # - proofs-tools + # name: <>-docker-release + # resource_class: xlarge + # docker_tags: <> + # publish: true + # release: true + # filters: + # tags: + # only: /^<>\/v.*/ + # branches: + # ignore: /.*/ + # context: + # - oplabs-gcr-release + # requires: + # - hold - develop-publish-contract-artifacts: - when: - or: - - equal: [ "develop", <> ] - - equal: [ true, <> ] - jobs: - - publish-contract-artifacts + # scheduled-todo-issues: + # when: + # equal: [build_four_hours, <>] + # jobs: + # - todo-issues: + # name: todo-issue-checks + # context: + # - slack - develop-fault-proofs: - when: - and: - - or: - - equal: [ "develop", <> ] - - equal: [ true, <> ] - - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - go-mod-download - - cannon-prestate: - requires: - - go-mod-download - - cannon-stf-verify: - requires: - - go-mod-download - context: - - slack - - contracts-bedrock-build: - skip_pattern: test - context: - - slack - - go-e2e-test: - name: op-e2e-cannon-tests - module: op-e2e - target: test-cannon - parallelism: 8 - notify: true - mentions: "@proofs-team" - requires: - - contracts-bedrock-build - - cannon-prestate - context: - - slack + # scheduled-fpp: + # when: + # equal: [build_hourly, <>] + # jobs: + # - fpp-verify: + # context: + # - slack + # - oplabs-fpp-nodes - develop-kontrol-tests: - when: - and: - - or: - - equal: [ "develop", <> ] - - equal: [ true, <> ] - - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - kontrol-tests: - context: - - slack - - runtimeverification + # develop-publish-contract-artifacts: + # when: + # or: + # - equal: ["develop", <>] + # - equal: + # [true, <>] + # jobs: + # - publish-contract-artifacts - scheduled-cannon-full-tests: - when: - or: - - equal: [ build_four_hours, <> ] - - equal: [ true, << pipeline.parameters.cannon_full_test_dispatch >> ] - jobs: - - contracts-bedrock-build: - skip_pattern: test - - cannon-go-lint-and-test: - requires: - - contracts-bedrock-build - context: - - slack + # develop-forge-coverage: + # when: + # and: + # - or: + # - equal: ["develop", <>] + # - equal: [true, <>] + # - not: + # equal: [scheduled_pipeline, << pipeline.trigger_source >>] + # jobs: + # - contracts-bedrock-coverage - scheduled-docker-publish: - when: - or: - - equal: [ build_hourly, <> ] - # Trigger on manual triggers if explicitly requested - - equal: [ true, << pipeline.parameters.docker_publish_dispatch >> ] - jobs: - - docker-build: - matrix: - parameters: - docker_name: - - op-node - - op-batcher - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - op-supervisor - - cannon - name: <>-docker-publish - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - check-cross-platform: - matrix: - parameters: - op_component: - - op-node - - op-batcher - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - op-supervisor - - cannon - name: <>-cross-platform - requires: - - <>-docker-publish - - docker-build: - name: contracts-bedrock-docker-publish - docker_name: contracts-bedrock - docker_tags: <>,<> - resource_class: xlarge - publish: true - context: - - oplabs-gcr - - slack + # develop-fault-proofs: + # when: + # and: + # - or: + # - equal: ["develop", <>] + # - equal: [true, <>] + # - not: + # equal: [scheduled_pipeline, << pipeline.trigger_source >>] + # jobs: + # - go-mod-download + # - cannon-prestate + # - cannon-stf-verify: + # requires: + # - go-mod-download + # context: + # - slack + # - contracts-bedrock-build: + # build_args: --deny-warnings --skip test + # context: + # - slack + # - go-tests: + # name: op-e2e-cannon-tests + # notify: true + # mentions: "@proofs-team" + # no_output_timeout: 60m + # test_timeout: 59m + # resource_class: ethereum-optimism/latitude-fps-1 + # environment_overrides: | + # export OP_E2E_CANNON_ENABLED="true" + # packages: | + # op-e2e/faultproofs + # context: + # - slack - scheduled-preimage-reproducibility: - when: - or: - - equal: [build_daily, <> ] - # Trigger on manual triggers if explicitly requested - - equal: [ true, << pipeline.parameters.reproducibility_dispatch >> ] - jobs: - - preimage-reproducibility: - matrix: - parameters: - version: - - "0.1.0" - - "0.2.0" - - "0.3.0" - - "1.0.0" - - "1.1.0" - - "1.2.0" - - "1.3.0-rc.1" - - "1.3.0-rc.2" - - "1.3.0-rc.3" - - "1.3.1-rc.1" - - "1.3.1-rc.2" - context: - slack + # develop-kontrol-tests: + # when: + # and: + # - or: + # - equal: ["develop", <>] + # - equal: [true, <>] + # - not: + # equal: [scheduled_pipeline, << pipeline.trigger_source >>] + # jobs: + # - kontrol-tests: + # context: + # - slack + # - runtimeverification + + # scheduled-cannon-full-tests: + # when: + # or: + # - equal: [build_four_hours, <>] + # - equal: [true, << pipeline.parameters.cannon_full_test_dispatch >>] + # jobs: + # - contracts-bedrock-build: + # build_args: --deny-warnings --skip test + # - cannon-go-lint-and-test: + # name: cannon-go-lint-and-test-<>-bit + # requires: + # - contracts-bedrock-build + # context: + # - slack + # matrix: + # parameters: + # mips_word_size: [32, 64] + + # scheduled-docker-publish: + # when: + # or: + # - equal: [build_hourly, <>] + # # Trigger on manual triggers if explicitly requested + # - equal: [true, << pipeline.parameters.docker_publish_dispatch >>] + # jobs: + # - docker-build: + # matrix: + # parameters: + # docker_name: + # - op-node + # - op-batcher + # - op-program + # - op-proposer + # - op-challenger + # - op-dispute-mon + # - op-conductor + # - op-supervisor + # - cannon + # name: <>-docker-publish + # docker_tags: <>,<> + # platforms: "linux/amd64,linux/arm64" + # publish: true + # context: + # - oplabs-gcr + # - slack + # - check-cross-platform: + # matrix: + # parameters: + # op_component: + # - op-node + # - op-batcher + # - op-program + # - op-proposer + # - op-challenger + # - op-dispute-mon + # - op-conductor + # - op-supervisor + # - cannon + # name: <>-cross-platform + # requires: + # - <>-docker-publish + + # scheduled-preimage-reproducibility: + # when: + # or: + # - equal: [build_daily, <>] + # # Trigger on manual triggers if explicitly requested + # - equal: [true, << pipeline.parameters.reproducibility_dispatch >>] + # jobs: + # - preimage-reproducibility: + # context: slack + + # scheduled-stale-check: + # when: + # or: + # - equal: [build_daily, <>] + # # Trigger on manual triggers if explicitly requested + # - equal: [true, << pipeline.parameters.stale_check_dispatch >>] + # jobs: + # - stale-check: + # context: github-token-stale-check diff --git a/.coderabbit.yml b/.coderabbit.yml deleted file mode 100644 index f54403cf1571e..0000000000000 --- a/.coderabbit.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: "en" -early_access: false -reviews: - high_level_summary: false - poem: false - review_status: false - collapse_walkthrough: true - path_filters: - - "!**/*.json" - path_instructions: - - path: "**.sol" - instructions: "Focus on the following areas: - - Security - - Identifying test cases which are lacking - - Removing unnecessary code - " - auto_review: - enabled: false - drafts: false - base_branches: - - "develop" - - "feat/*" -chat: - auto_reply: true diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index 4c8918566485a..0000000000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: Setup -description: Common setup steps used by our workflows -runs: - using: composite - steps: - - name: Setup foundry - uses: foundry-rs/foundry-toolchain@v1 diff --git a/.github/workflows/close-stale.yml b/.github/workflows/close-stale.yml deleted file mode 100644 index 68e8b4ec82c56..0000000000000 --- a/.github/workflows/close-stale.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: 'Close stale issues and PRs' -on: - schedule: - - cron: '30 1 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v9 - with: - stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days.' - stale-issue-label: 'S-stale' - exempt-pr-labels: 'S-exempt-stale' - days-before-issue-stale: 999 - days-before-pr-stale: 14 - days-before-close: 5 - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/es-release.yml b/.github/workflows/es-release.yml new file mode 100644 index 0000000000000..54dab0506bcb8 --- /dev/null +++ b/.github/workflows/es-release.yml @@ -0,0 +1,29 @@ +# This workflow will publish a github release for optimism + +name: Publish +run-name: ${{ github.actor }} is publishing a release 🚀 +on: + push: + tags: + - 'v*' + +# Always wait for previous release to finish before releasing again +concurrency: ${{ github.workflow }}-${{ github.ref }} + + +jobs: + build: + runs-on: ubuntu-latest + env: + BUILD_DIR: optimism.${{ github.ref_name }} + BIN_DIR: optimism.${{ github.ref_name }}/build/bin + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref }} + name: Release ${{ github.ref_name }} + generate_release_notes: true \ No newline at end of file diff --git a/.github/workflows/tag-service.yml b/.github/workflows/tag-service.yml deleted file mode 100644 index 439b48f13d4e6..0000000000000 --- a/.github/workflows/tag-service.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Tag Service - -on: - workflow_dispatch: - inputs: - bump: - description: 'How much to bump the version by' - required: true - type: choice - options: - - major - - minor - - patch - - prerelease - - finalize-prerelease - service: - description: 'Which service to release' - required: true - type: choice - options: - - ci-builder - - ci-builder-rust - - op-node - - op-batcher - - op-proposer - - op-challenger - - op-program - - op-dispute-mon - - op-ufm - - da-server - - op-contracts - - op-conductor - prerelease: - description: Increment major/minor/patch as prerelease? - required: false - type: boolean - default: false - -permissions: - contents: write - -jobs: - release: - runs-on: ubuntu-latest - environment: op-stack-production - - steps: - - uses: actions/checkout@v4 - - name: Fetch tags - run: git fetch --tags origin --force - - name: Setup Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install deps - run: pip install -r requirements.txt - working-directory: ops/tag-service - - run: ops/tag-service/tag-service.py --bump="$BUMP" --service="$SERVICE" - env: - INPUT_GITHUB_TOKEN: ${{ github.token }} - BUMP: ${{ github.event.inputs.bump }} - SERVICE: ${{ github.event.inputs.service }} - if: ${{ github.event.inputs.prerelease == 'false' }} - - run: ops/tag-service/tag-service.py --bump="$BUMP" --service="$SERVICE" --pre-release - env: - INPUT_GITHUB_TOKEN: ${{ github.token }} - BUMP: ${{ github.event.inputs.bump }} - SERVICE: ${{ github.event.inputs.service }} - if: ${{ github.event.inputs.prerelease == 'true' }} diff --git a/.gitmodules b/.gitmodules index 21ecaedbb77a8..909027324feab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,9 @@ [submodule "packages/contracts-bedrock/lib/openzeppelin-contracts-v5"] path = packages/contracts-bedrock/lib/openzeppelin-contracts-v5 url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "packages/contracts-bedrock/lib/solady-v0.0.245"] + path = packages/contracts-bedrock/lib/solady-v0.0.245 + url = https://github.com/vectorized/solady +[submodule "da-server"] + path = da-server + url = https://github.com/ethstorage/da-server diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml new file mode 100644 index 0000000000000..177fabb6fa8da --- /dev/null +++ b/.semgrep/rules/sol-rules.yaml @@ -0,0 +1,197 @@ +rules: + - id: sol-safety-deployutils-args + languages: [solidity] + severity: ERROR + message: _args parameter should be wrapped with DeployUtils.encodeConstructor + pattern-regex: DeployUtils\.(create1|create2|create1AndSave|create2AndSave)\s*\(\s*\{[^}]*?_args\s*:\s*(?!\s*DeployUtils\.encodeConstructor\()\s*[^}]*?\}\s*\) + + - id: sol-safety-expectrevert-before-ll-call + languages: [solidity] + severity: ERROR + message: vm.expectRevert is followed by a low-level call but not followed by assertion expecting revert + patterns: + - pattern-either: + - pattern: | + vm.expectRevert(...); + $CALL + $CHECK + - pattern: | + vm.expectRevert(...); + $CALL + - metavariable-pattern: + metavariable: $CALL + patterns: + - pattern-regex: \.call\(.*\)|\.delegatecall\(.*\) + - pattern-not-inside: + patterns: + - pattern: | + vm.expectRevert(...); + $CALL; + assertTrue(revertsAsExpected); + + - id: sol-safety-expectrevert-no-args + languages: [solidity] + severity: ERROR + message: vm.expectRevert() must specify the revert reason + patterns: + - pattern: vm.expectRevert() + paths: + exclude: + - packages/contracts-bedrock/test/dispute/WETH98.t.sol + + - id: sol-safety-natspec-semver-match + languages: [generic] + severity: ERROR + message: Semgrep defined in contract must match natspec $VERSION1 $VERSION2 + patterns: + - pattern-either: + - pattern-regex: /// @custom:semver + (?P[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.]+)?)\s+string + public constant version = + "(?P[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.]+)?)"; + - pattern-regex: /// @custom:semver + (?P[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.]+)?)\s+function + version\(\) public pure virtual returns \(string memory\) + \{\s+return + "(?P[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.]+)?)"; + - pattern-regex: + /// @custom:semver (?P[a-zA-Z0-9.+-]+)\s+function + version\(\) public pure override returns \(string memory\) + \{\s+return string\.concat\(super\.version\(\), + "(?P[a-zA-Z0-9.+-]+)"\); + - metavariable-comparison: + comparison: $VERSION1 != $VERSION2 + metavariable: $VERSION1 + paths: + include: + - packages/contracts-bedrock/src + + - id: sol-safety-no-public-in-libraries + languages: [generic] + severity: ERROR + message: Public functions in libraries are not allowed + patterns: + - pattern-inside: | + library $LIBRARY { + ... + } + - pattern-regex: function\s+\w+\s*\([^)]*\)\s+(?:.*\s+)?(public|external)\s+.*\{ + + - id: sol-style-input-arg-fmt + languages: [solidity] + severity: ERROR + message: Named inputs to functions must be prepended with an underscore + pattern-regex: function\s+\w+\s*\(\s*([^)]*?\b\w+\s+(?!_)(?!memory\b)(?!calldata\b)(?!storage\b)(?!payable\b)\w+\s*(?=,|\))) + paths: + exclude: + - packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC721.sol + - packages/contracts-bedrock/interfaces/universal/IWETH98.sol + - packages/contracts-bedrock/interfaces/dispute/IDelayedWETH.sol + - op-chain-ops/script/testdata/scripts/ScriptExample.s.sol + - packages/contracts-bedrock/test + - packages/contracts-bedrock/scripts/libraries/Solarray.sol + - packages/contracts-bedrock/scripts/interfaces/IGnosisSafe.sol + - packages/contracts-bedrock/interfaces/universal/IWETH.sol + - packages/contracts-bedrock/src/universal/WETH98.sol + - packages/contracts-bedrock/interfaces/L2/ISuperchainWETH.sol + - packages/contracts-bedrock/src/L2/SuperchainWETH.sol + - packages/contracts-bedrock/interfaces/governance/IGovernanceToken.sol + - packages/contracts-bedrock/src/governance/GovernanceToken.sol + + - id: sol-style-return-arg-fmt + languages: [solidity] + severity: ERROR + message: Named return arguments to functions must be appended with an underscore + pattern-regex: returns\s*(\w+\s*)?\(\s*([^)]*?\b\w+\s+(?!memory\b)(?!calldata\b)(?!storage\b)(?!payable\b)\w+(?" to assert that the rule catches the code. +// Use comments like "ok: " to assert that the rule does not catch the code. + +/// begin SemgrepTest__sol-style-no-bare-imports +// ok: sol-style-no-bare-imports +import { SomeStruct } from "some-library.sol"; + +// ok: sol-style-no-bare-imports +import { SomeStruct, AnotherThing } from "some-library.sol"; + +// ok: sol-style-no-bare-imports +import { SomeStruct as SomeOtherStruct } from "some-library.sol"; + +// ok: sol-style-no-bare-imports +import { SomeStruct as SomeOtherStruct, AnotherThing as AnotherOtherThing } from "some-library.sol"; + +// ok: sol-style-no-bare-imports +import { SomeStruct as SomeOtherStruct, AnotherThing } from "some-library.sol"; + +// ok: sol-style-no-bare-imports +import { AnotherThing, SomeStruct as SomeOtherStruct } from "some-library.sol"; + +// ruleid: sol-style-no-bare-imports +import "some-library.sol"; +/// end SemgrepTest__sol-style-no-bare-imports + +contract SemgrepTest__sol_safety_deployutils_args { + function test() { + // ruleid: sol-safety-deployutils-args + DeployUtils.create1AndSave({ + _save: this, + _name: "SuperchainConfig", + _args: abi.encodeCall(ISuperchainConfig.__constructor__, ()) + }); + + // ruleid: sol-safety-deployutils-args + DeployUtils.create1({ _name: "SuperchainConfig", _args: abi.encodeCall(ISuperchainConfig.__constructor__, ()) }); + + // ruleid: sol-safety-deployutils-args + DeployUtils.create2AndSave({ + _save: this, + _salt: _implSalt(), + _name: "SuperchainConfig", + _args: abi.encodeCall(ISuperchainConfig.__constructor__, ()) + }); + + // ruleid: sol-safety-deployutils-args + DeployUtils.create2({ + _salt: _implSalt(), + _name: "SuperchainConfig", + _args: abi.encodeCall(ISuperchainConfig.__constructor__, ()) + }); + + // ok: sol-safety-deployutils-args + DeployUtils.create1AndSave({ + _save: this, + _name: "Proxy", + _nick: "DataAvailabilityChallengeProxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (proxyAdmin))) + }); + + // ok: sol-safety-deployutils-args + DeployUtils.create1({ + _name: "Proxy", + _nick: "DataAvailabilityChallengeProxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (proxyAdmin))) + }); + + // ok: sol-safety-deployutils-args + DeployUtils.create2AndSave({ + _save: this, + _salt: _implSalt(), + _name: "Proxy", + _nick: "DataAvailabilityChallengeProxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (proxyAdmin))) + }); + + // ok: sol-safety-deployutils-args + DeployUtils.create2({ + _salt: _implSalt(), + _name: "Proxy", + _nick: "DataAvailabilityChallengeProxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (proxyAdmin))) + }); + } +} + +contract SemgrepTest__sol_safety_expectrevert_before_ll_call { + function test() { + // ok: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + (bool revertsAsExpected,) = target.call(hex""); + assertTrue(revertsAsExpected); + + // ok: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + (bool revertsAsExpected,) = target.delegatecall(hex""); + assertTrue(revertsAsExpected); + + // ok: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + target.someFunction(); + + // ruleid: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + (bool success,) = target.call(hex""); + + // ruleid: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + (bool success,) = target.call(hex""); + assertTrue(success); + + // ruleid: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + (bool success,) = target.delegatecall(hex""); + assertTrue(success); + + // ruleid: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + target.call(hex""); + + // ruleid: sol-safety-expectrevert-before-ll-call + vm.expectRevert("some revert"); + target.delegatecall(hex""); + } +} + +contract SemgrepTest__sol_safety_expectrevert_no_args { + function test() { + // ok: sol-safety-expectrevert-no-args + vm.expectRevert("some revert"); + target.someFunction(); + + // ruleid: sol-safety-expectrevert-no-args + vm.expectRevert(); + target.someFunction(); + } +} + +contract SemgrepTest__sol_safety_natspec_semver_match { + // ok: sol-safety-natspec-semver-match + /// @custom:semver 2.8.1-beta.4 + string public constant version = "2.8.1-beta.4"; + + // ok: sol-safety-natspec-semver-match + /// @custom:semver 2.8.1-beta.3 + function version() public pure virtual returns (string memory) { + return "2.8.1-beta.3"; + } + + // ok: sol-safety-natspec-semver-match + /// @custom:semver +interop-beta.1 + function version() public pure override returns (string memory) { + return string.concat(super.version(), "+interop-beta.1"); + } + + // ruleid: sol-safety-natspec-semver-match + /// @custom:semver 2.8.1-beta.5 + string public constant version = "2.8.1-beta.4"; + + // ruleid: sol-safety-natspec-semver-match + /// @custom:semver 2.8.1-beta.4 + function version() public pure virtual returns (string memory) { + return "2.8.1-beta.3"; + } + + // ruleid: sol-safety-natspec-semver-match + /// @custom:semver +interop-beta.2 + function version() public pure override returns (string memory) { + return string.concat(super.version(), "+interop-beta.1"); + } +} + +library SemgrepTest__sol_safety_no_public_in_libraries { + // ok: sol-safety-no-public-in-libraries + function test() internal { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test() private { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test(uint256 _value, address _addr) internal { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test(uint256 _value, address _addr) private { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test() internal pure returns (uint256) { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test() private pure returns (uint256) { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test() internal view returns (uint256, address) { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test() private view returns (uint256, address) { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test() internal returns (uint256 amount_, bool success_) { + // ... + } + + // ok: sol-safety-no-public-in-libraries + function test() private returns (uint256 amount_, bool success_) { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() public { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() external { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() public pure { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() external pure { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() public view { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() external view { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test(uint256 _value, address _addr) public { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test(uint256 _value, address _addr) external { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() public pure returns (uint256) { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() external pure returns (uint256) { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() public view returns (uint256, address) { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() external view returns (uint256, address) { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() public returns (uint256 amount_, bool success_) { + // ... + } + + // ruleid: sol-safety-no-public-in-libraries + function test() external returns (uint256 amount_, bool success_) { + // ... + } +} + +contract SemgrepTest__sol_style_input_arg_fmt { + // ok: sol-style-input-arg-fmt + event Test(address indexed src, address indexed guy, uint256 wad); + + // ok: sol-style-input-arg-fmt + function test() public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(address payable) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(uint256 _a, uint256 _b) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(uint256 _a, uint256 _b) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(bytes memory _a) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(bytes memory _a, uint256 _b) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(Contract.Struct memory _a) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(uint256 _b, bytes memory) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(bytes memory, uint256 _b) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test(bytes memory) public { + // ... + } + + // ok: sol-style-input-arg-fmt + function test() public returns (bytes memory b_) { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function test(uint256 a) public { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function test(uint256 a, uint256 b) public { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function test(bytes memory a) public { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function testg(bytes memory a, uint256 b) public { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function test(uint256 b, bytes memory a) public { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function test(Contract.Struct memory a) public { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function test(uint256 _a, uint256 b) public { + // ... + } + + // ruleid: sol-style-input-arg-fmt + function test(uint256 a, uint256 _b) public { + // ... + } +} + +contract SemgrepTest__sol_style_return_arg_fmt { + // ok: sol-style-return-arg-fmt + function test() returns (uint256 a_) { + // ... + } + + // ok: sol-style-return-arg-fmt + function test() returns (address payable) { + // ... + } + + // ok: sol-style-return-arg-fmt + function test() returns (uint256 a_, bytes memory b_) { + // ... + } + + // ok: sol-style-return-arg-fmt + function test() returns (Contract.Struct memory ab_) { + // ... + } + + // ok: sol-style-return-arg-fmt + function test() returns (uint256, bool) { + // ... + } + + // ok: sol-style-return-arg-fmt + function test() returns (uint256) { + // ... + } + + // ruleid: sol-style-return-arg-fmt + function test() returns (uint256 a) { + // ... + } + + // ruleid: sol-style-return-arg-fmt + function test() returns (uint256 a, bytes memory b) { + // ... + } + + // ruleid: sol-style-return-arg-fmt + function test() returns (Contract.Struct memory b) { + // ... + } + + // ruleid: sol-style-return-arg-fmt + function test() returns (Contract.Struct memory b, bool xyz) { + // ... + } +} + +contract SemgrepTest__sol_style_doc_comment { + function test() { + // ok: sol-style-doc-comment + /// Good comment + + // ok: sol-style-doc-comment + /// Multiline + /// Good + /// comment + /// @notice with natspec + + // ruleid: sol-style-doc-comment + /** + * Example bad comment + */ + + // ruleid: sol-style-doc-comment + /** + * Example + * bad + * Multiline + * comment + */ + + // ruleid: sol-style-doc-comment + /** + * Example + * bad + * Multiline + * comment + * @notice with natspec + */ + } +} + +contract SemgrepTest__sol_style_malformed_require { + function test() { + // ok: sol-style-malformed-require + require(cond, "MyContract: test message good"); + + // ok: sol-style-malformed-require + require(cond, "MyContract: test message good"); + + // ok: sol-style-malformed-require + require(!LibString.eq(_standardVersionsToml, ""), "DeployImplementationsInput: not set"); + + // ok: sol-style-malformed-require + require(cond, "MyContract: Test message"); + + // ok: sol-style-malformed-require + require(cond, "L1SB-10"); + + // ok: sol-style-malformed-require + require(cond, "CHECK-L2OO-140"); + + // ok: sol-style-malformed-require + require(bytes(env_).length > 0, "Config: must set DEPLOY_CONFIG_PATH to filesystem path of deploy config"); + + // ok: sol-style-malformed-require + require(false, string.concat("DeployConfig: cannot find deploy config file at ", _path)); + + // ok: sol-style-malformed-require + require( + _addrs[i] != _addrs[j], + string.concat( + "DeployUtils: check failed, duplicates at ", LibString.toString(i), ",", LibString.toString(j) + ) + ); + + // ruleid: sol-style-malformed-require + require(cond, "MyContract: "); + + // ruleid: sol-style-malformed-require + require(cond, "test"); + } +} + +contract SemgrepTest__sol_style_malformed_revert { + function test() { + // ok: sol-style-malformed-revert + revert("MyContract: test message good"); + + // ok: sol-style-malformed-revert + revert("MyContract: test message good"); + + // ok: sol-style-malformed-revert + revert("DeployImplementationsInput: not set"); + + // ok: sol-style-malformed-revert + revert("MyContract: Test message"); + + // ok: sol-style-malformed-revert + revert("L1SB-10"); + + // ok: sol-style-malformed-revert + revert("CHECK-L2OO-140"); + + // ok: sol-style-malformed-revert + revert(); + + // ok: sol-style-malformed-revert + revert("Config: must set DEPLOY_CONFIG_PATH to filesystem path of deploy config"); + + // ok: sol-style-malformed-revert + revert(string.concat("DeployConfig: cannot find deploy config file at ", _path)); + + // ok: sol-style-malformed-revert + revert( + string.concat( + "DeployUtils: check failed, duplicates at ", LibString.toString(i), ",", LibString.toString(j) + ) + ); + + // ruleid: sol-style-malformed-revert + revert("MyContract: "); + + // ruleid: sol-style-malformed-revert + revert("test"); + } +} + +contract SemgrepTest__sol_style_enforce_require_msg { + function test() { + // ok: sol-style-enforce-require-msg + require(cond, "MyContract: test message good"); + + // ruleid: sol-style-enforce-require-msg + require(cond); + } +} diff --git a/.semgrepignore b/.semgrepignore index eacb68af1e03d..6c3e11fae449c 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -8,13 +8,11 @@ vendor/ .tox/ *.min.js -# Common test paths -# TODO: Tests should conform to semgrep too. -test/ -tests/ - # Semgrep rules folder -.semgrep +.semgrep/ # Semgrep-action log folder .semgrep_logs/ + +# Test contracts the scripts folder +op-chain-ops/script/testdata/scripts/ \ No newline at end of file diff --git a/.snyk b/.snyk deleted file mode 100644 index 1558a7a82432a..0000000000000 --- a/.snyk +++ /dev/null @@ -1,3 +0,0 @@ -exclude: - global: - - infra/op-replica/** # snyk does not respect kustomizations, so not useful here diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0534e696ff5f7..2fcb5480c1ae7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,7 @@ You can: - Report issues in this repository. Great bug reports are detailed and give clear instructions for how a developer can reproduce the problem. Write good bug reports and developers will love you. - **IMPORTANT**: If you believe your report impacts the security of this repository, refer to the canonical [Security Policy](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md) document. - Fix issues that are tagged as [`D-good-first-issue`](https://github.com/ethereum-optimism/optimism/labels/D-good-first-issue) or [`S-confirmed`](https://github.com/ethereum-optimism/optimism/labels/S-confirmed). +- Larger projects are listed on [this project board](https://github.com/orgs/ethereum-optimism/projects/31/views/9). Please talk to us if you're considering working on one of these, they may not be fully specified so it will reduce risk to discuss the approach and ensure that it's still relevant. - Help improve the [Optimism Developer Docs](https://github.com/ethereum-optimism/docs) by reporting issues, fixing typos, or adding missing sections. - Get involved in the protocol design process by joining discussions within the [OP Stack Specs](https://github.com/ethereum-optimism/specs/discussions) repository. @@ -19,63 +20,54 @@ Interactions within this repository are subject to a [Code of Conduct](https://g ## Development Quick Start -### Software Dependencies - -| Dependency | Version | Version Check Command | -| ------------------------------------------------------------- | -------- | ------------------------ | -| [git](https://git-scm.com/) | `^2` | `git --version` | -| [go](https://go.dev/) | `^1.21` | `go version` | -| [node](https://nodejs.org/en/) | `^20` | `node --version` | -| [nvm](https://github.com/nvm-sh/nvm) | `^0.39` | `nvm --version` | -| [just](https://github.com/casey/just) | `^1.34.0`| `just --version` | -| [foundry](https://github.com/foundry-rs/foundry#installation) | `^0.2.0` | `forge --version` | -| [make](https://linux.die.net/man/1/make) | `^3` | `make --version` | -| [jq](https://github.com/jqlang/jq) | `^1.6` | `jq --version` | -| [direnv](https://direnv.net) | `^2` | `direnv --version` | -| [docker](https://docs.docker.com/get-docker/) | `^24` | `docker --version` | -| [docker compose](https://docs.docker.com/compose/install/) | `^2.23` | `docker compose version` | +### Setting Up -### Notes on Specific Dependencies +Clone the repository and open it: -#### `node` +```bash +git clone git@github.com:ethereum-optimism/optimism.git +cd optimism +``` -Make sure to use the version of `node` specified within [`.nvmrc`](./.nvmrc). -You can use [`nvm`](https://github.com/nvm-sh/nvm) to manage multiple versions of Node.js on your machine and automatically switch to the correct version when you enter this repository. +### Software Dependencies -#### `foundry` +You will need to install a number of software dependencies to effectively contribute to the +Optimism Monorepo. We use [`mise`](https://mise.jdx.dev/) as a dependency manager for these tools. +Once properly installed, `mise` will provide the correct versions for each tool. `mise` does not +replace any other installations of these binaries and will only serve these binaries when you are +working inside of the `optimism` directory. -`foundry` is updated frequently and occasionally contains breaking changes. -This repository pins a specific version of `foundry` inside of [`versions.json`](./versions.json). -Use the command `just update-foundry` at the root of the monorepo to make sure that your version of `foundry` is the same as the one currently being used in CI. +#### Install `mise` -#### `direnv` +Install `mise` by following the instructions provided on the +[Getting Started page](https://mise.jdx.dev/getting-started.html#_1-install-mise-cli). -[`direnv`](https://direnv.net) is a tool used to load environment variables from [`.envrc`](./.envrc) into your shell so you don't have to manually export variables every time you want to use them. -`direnv` only has access to files that you explicitly allow it to see. -After [installing `direnv`](https://direnv.net/docs/installation.html), you will need to **make sure that [`direnv` is hooked into your shell](https://direnv.net/docs/hook.html)**. -Make sure you've followed [the guide on the `direnv` website](https://direnv.net/docs/hook.html), then **close your terminal and reopen it** so that the changes take effect (or `source` your config file if you know how to do that). +#### Trust the `mise.toml` file -#### `docker compose` +`mise` requires that you explicitly trust the `mise.toml` file which lists the dependencies that +this repository uses. After you've installed `mise` you'll be able to trust the file via: -[Docker Desktop](https://docs.docker.com/get-docker/) should come with `docker compose` installed by default. -You'll have to install the `compose` plugin if you're not using Docker Desktop or you're on linux. +```bash +mise trust mise.toml +``` -### Setting Up +#### Install dependencies -Clone the repository and open it: +Use `mise` to install the correct versions for all of the required tools: ```bash -git clone git@github.com:ethereum-optimism/optimism.git -cd optimism +mise install ``` -### Building the Monorepo +#### Installing updates -Make sure that you've installed all of the required [Software Dependencies](#software-dependencies) before you continue. -You will need [foundry](https://github.com/foundry-rs/foundry) to build the smart contracts found within this repository. -Refer to the note on [foundry as a dependency](#foundry) for instructions. +`mise` will notify you if any dependencies are outdated. Simply run `mise install` again to install +the latest versions of the dependencies if you receive these notifications. + +### Building the Monorepo -Install dependencies and build all packages within the monorepo by running: +You must install all of the required [Software Dependencies](#software-dependencies) to build the +Optimism Monorepo. Once you've done so, run the following command to build: ```bash make build @@ -87,7 +79,7 @@ Use the above command to rebuild the monorepo. ### Running tests -Before running tests: **follow the above instructions to get everything built.** +Before running tests: **follow the above instructions to get everything built**. #### Running unit tests (solidity) diff --git a/Makefile b/Makefile index c1e94ac8ca6f2..bcbe3e71f9be9 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +# provide JUSTFLAGS for just-backed targets +include ./just/flags.mk + COMPOSEFLAGS=-d ITESTS_L2_HOST=http://localhost:9545 BEDROCK_TAGS_REMOTE?=origin @@ -94,7 +97,7 @@ submodules: ## Updates git submodules op-node: ## Builds op-node binary - make -C ./op-node op-node + just $(JUSTFLAGS) ./op-node/op-node .PHONY: op-node generate-mocks-op-node: ## Generates mocks for op-node @@ -106,11 +109,11 @@ generate-mocks-op-service: ## Generates mocks for op-service .PHONY: generate-mocks-op-service op-batcher: ## Builds op-batcher binary - make -C ./op-batcher op-batcher + just $(JUSTFLAGS) ./op-batcher/op-batcher .PHONY: op-batcher op-proposer: ## Builds op-proposer binary - make -C ./op-proposer op-proposer + just $(JUSTFLAGS) ./op-proposer/op-proposer .PHONY: op-proposer op-challenger: ## Builds op-challenger binary @@ -147,8 +150,8 @@ cannon-prestate: op-program cannon ## Generates prestate using cannon and op-pro mv op-program/bin/0.json op-program/bin/prestate-proof.json .PHONY: cannon-prestate -cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the multithreaded cannon format - ./cannon/bin/cannon load-elf --type multithreaded --path op-program/bin/op-program-client.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json +cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the multithreaded64 cannon format + ./cannon/bin/cannon load-elf --type multithreaded64 --path op-program/bin/op-program-client64.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json ./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json --proof-fmt 'op-program/bin/%d-mt.json' --output "" mv op-program/bin/0-mt.json op-program/bin/prestate-proof-mt.json .PHONY: cannon-prestate-mt @@ -164,6 +167,7 @@ mod-tidy: ## Cleans up unused dependencies in Go modules clean: ## Removes all generated files under bin/ rm -rf ./bin + cd packages/contracts-bedrock/ && forge clean .PHONY: clean nuke: clean devnet-clean ## Completely clean the project directory @@ -172,10 +176,7 @@ nuke: clean devnet-clean ## Completely clean the project directory ## Prepares for running a local devnet pre-devnet: submodules $(DEVNET_CANNON_PRESTATE_FILES) - @if ! [ -x "$(command -v geth)" ]; then \ - make install-geth; \ - fi - @if ! [ -x "$(command -v eth2-testnet-genesis)" ]; then \ + @if ! [ -x "$$(command -v eth2-testnet-genesis)" ]; then \ make install-eth2-testnet-genesis; \ fi .PHONY: pre-devnet @@ -186,10 +187,6 @@ devnet-up: pre-devnet ## Starts the local devnet PYTHONPATH=./bedrock-devnet $(PYTHON) ./bedrock-devnet/main.py --monorepo-dir=. .PHONY: devnet-up -devnet-test: pre-devnet ## Runs tests on the local devnet - make -C op-e2e test-devnet -.PHONY: devnet-test - devnet-down: ## Stops the local devnet @(cd ./ops-bedrock && GENESIS_TIMESTAMP=$(shell date +%s) docker compose stop) .PHONY: devnet-down @@ -250,14 +247,6 @@ update-op-geth: ## Updates the Geth version used in the project ./ops/scripts/update-op-geth.py .PHONY: update-op-geth -install-geth: ## Installs or updates Geth if versions do not match - ./ops/scripts/geth-version-checker.sh && \ - (echo "Geth versions match, not installing geth..."; true) || \ - (echo "Versions do not match, installing geth!"; \ - go install -v github.com/ethereum/go-ethereum/cmd/geth@$(shell jq -r .geth < versions.json); \ - echo "Installed geth!"; true) -.PHONY: install-geth - install-eth2-testnet-genesis: - go install -v github.com/protolambda/eth2-testnet-genesis@$(shell jq -r .eth2_testnet_genesis < versions.json) + go install -v github.com/protolambda/eth2-testnet-genesis@v$(shell yq '.tools."go:github.com/protolambda/eth2-testnet-genesis"' mise.toml) .PHONY: install-eth2-testnet-genesis diff --git a/README.md b/README.md index d33f920c8398b..bda740f63c2ce 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The OP Stack is a collaborative project. By collaborating on free, open software [CONTRIBUTING.md](./CONTRIBUTING.md) contains a detailed explanation of the contributing process for this repository. Make sure to use the [Developer Quick Start](./CONTRIBUTING.md#development-quick-start) to properly set up your development environment. -[Good First Issues](https://github.com/ethereum-optimism/optimism/issues?q=is:open+is:issue+label:D-good-first-issue) are a great place to look for tasks to tackle if you're not sure where to start. +[Good First Issues](https://github.com/ethereum-optimism/optimism/issues?q=is:open+is:issue+label:D-good-first-issue) are a great place to look for tasks to tackle if you're not sure where to start, and see [CONTRIBUTING.md](./CONTRIBUTING.md) for info on larger projects. ## Security Policy and Vulnerability Reporting @@ -65,7 +65,6 @@ The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vuln
 ├── docs: A collection of documents including audits and post-mortems
 ├── op-batcher: L2-Batch Submitter, submits bundles of batches to L1
-├── op-bootnode: Standalone op-node discovery bootnode
 ├── op-chain-ops: State surgery utilities
 ├── op-challenger: Dispute game challenge agent
 ├── op-e2e: End-to-End testing of all bedrock components in Go
@@ -80,8 +79,7 @@ The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vuln
 ├── ops-bedrock: Bedrock devnet work
 ├── packages
 │   ├── contracts-bedrock: OP Stack smart contracts
-├── proxyd: Configurable RPC request router and proxy
-├── specs: Specs of the rollup starting at the Bedrock upgrade
+├── semgrep: Semgrep rules and tests
 
## Development and Release Process @@ -125,7 +123,7 @@ All other components and packages should be considered development components on ### Development branch The primary development branch is [`develop`](https://github.com/ethereum-optimism/optimism/tree/develop/). -`develop` contains the most up-to-date software that remains backwards compatible with the latest experimental [network deployments](https://community.optimism.io/docs/useful-tools/networks/). +`develop` contains the most up-to-date software that remains backwards compatible with the latest experimental [network deployments](https://docs.optimism.io/chain/networks). If you're making a backwards compatible change, please direct your pull request towards `develop`. **Changes to contracts within `packages/contracts-bedrock/src` are usually NOT considered backwards compatible.** diff --git a/bedrock-devnet/devnet/__init__.py b/bedrock-devnet/devnet/__init__.py index 8b21c37904dd0..39a7e824b2dff 100644 --- a/bedrock-devnet/devnet/__init__.py +++ b/bedrock-devnet/devnet/__init__.py @@ -24,7 +24,7 @@ log = logging.getLogger() # Global constants -FORKS = ["delta", "ecotone", "fjord", "granite"] +FORKS = ["delta", "ecotone", "fjord", "granite", "holocene"] # Global environment variables DEVNET_NO_BUILD = os.getenv('DEVNET_NO_BUILD') == "true" @@ -259,8 +259,8 @@ def devnet_deploy(paths): # Selectively set the L2OO_ADDRESS or DGF_ADDRESS if using L2OO. # Must be done selectively because op-proposer throws if both are set. if DEVNET_L2OO: - docker_env['L2OO_ADDRESS'] = l2_output_oracle l2_output_oracle = addresses['L2OutputOracleProxy'] + docker_env['L2OO_ADDRESS'] = l2_output_oracle log.info(f'Using L2OutputOracle {l2_output_oracle}') else: dispute_game_factory = addresses['DisputeGameFactoryProxy'] diff --git a/cannon/Dockerfile.diff b/cannon/Dockerfile.diff index dcdd3587e6817..a158ef9df79bb 100644 --- a/cannon/Dockerfile.diff +++ b/cannon/Dockerfile.diff @@ -23,7 +23,7 @@ ARG GIT_DATE ARG TARGETOS TARGETARCH -FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.1.0-alpha.1 AS cannon-v2 +FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.1.0-alpha.4 AS cannon-v2 FROM --platform=$BUILDPLATFORM builder as cannon-verify COPY --from=cannon-v2 /usr/local/bin/cannon /usr/local/bin/cannon-v2 diff --git a/cannon/Makefile b/cannon/Makefile index 5376b1b62086d..6156cd68d64e4 100644 --- a/cannon/Makefile +++ b/cannon/Makefile @@ -15,12 +15,22 @@ endif .DEFAULT_GOAL := cannon +# The MIPS64 r1 opcodes not supported by cannon. This list does not include coprocess-specific opcodes. +UNSUPPORTED_OPCODES := (dclo|dclz) + +CANNON32_FUZZTIME := 10s +CANNON64_FUZZTIME := 20s + cannon32-impl: env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build --tags=cannon32 -v $(LDFLAGS) -o ./bin/cannon32-impl . cannon64-impl: env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build --tags=cannon64 -v $(LDFLAGS) -o ./bin/cannon64-impl . +# Note: This target is used by ./scripts/build-legacy-cannons.sh +# It should build the individual versions of cannons and copy them into place in hte multicannon/embeds directory +# Ideally, preserve backwards compatibility with this behaviour but if it needs to change, build-legacy-cannons.sh will +# need to be updated to account for different behaviours in different versions. cannon-embeds: cannon32-impl cannon64-impl # singlethreaded-v2 @cp bin/cannon32-impl ./multicannon/embeds/cannon-2 @@ -39,7 +49,7 @@ elf: make -C ./testdata/example elf sanitize-program: - @if ! { mips-linux-gnu-objdump -d -j .text $$GUEST_PROGRAM | awk '{print $3}' | grep -Ew -m1 '(bgezal|bltzal)'; }; then \ + @if ! { mips-linux-gnu-objdump -d -j .text $$GUEST_PROGRAM | awk '{print $3}' | grep -Ew -m1 "$(UNSUPPORTED_OPCODES)"; }; then \ echo "guest program is sanitized for unsupported instructions"; \ else \ echo "found unsupported instructions in the guest program"; \ @@ -49,9 +59,12 @@ sanitize-program: contract: cd ../packages/contracts-bedrock && forge build -test: elf contract +test: elf contract test64 go test -v ./... +test64: elf contract + go test -tags=cannon64 -run '(TestEVM.*64|TestHelloEVM|TestClaimEVM)' ./mipsevm/tests + diff-%-cannon: cannon elf $$OTHER_CANNON load-elf --type $* --path ./testdata/example/bin/hello.elf --out ./bin/prestate-other.bin.gz --meta "" ./bin/cannon load-elf --type $* --path ./testdata/example/bin/hello.elf --out ./bin/prestate.bin.gz --meta "" @@ -76,19 +89,30 @@ cannon-stf-verify: @docker build --progress plain -f Dockerfile.diff ../ fuzz: - # Common vm tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk ./mipsevm/tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap ./mipsevm/tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup ./mipsevm/tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFcntl ./mipsevm/tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./mipsevm/tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead ./mipsevm/tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./mipsevm/tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite ./mipsevm/tests - # Single-threaded tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneST ./mipsevm/tests - # Multi-threaded tests - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests + printf "%s\n" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallBrk ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallMmap ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallExitGroup ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallFcntl ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateHintRead ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateHintWrite ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallCloneST ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON32_FUZZTIME) -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateConsistencyMulOp ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateConsistencyMultOp ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateConsistencyMultuOp ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallBrk ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallMmap ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallExitGroup ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallFcntl ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateHintRead ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStatePreimageRead ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateHintWrite ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStatePreimageWrite ./mipsevm/tests" \ + "go test $(FUZZLDFLAGS) --tags=cannon64 -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests" \ + | parallel -j 8 {} .PHONY: \ cannon32-impl \ diff --git a/cannon/README.md b/cannon/README.md index be1615b45f8a9..a5af5b2e7ece9 100644 --- a/cannon/README.md +++ b/cannon/README.md @@ -39,6 +39,7 @@ make cannon # Note: # - The L2 RPC is an archive L2 node on OP MAINNET. # - The L1 RPC is a non-archive RPC, also change `--l1.rpckind` to reflect the correct L1 RPC type. +# - The network flag is only suitable for specific networks(https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json). If you are running on the devnet, please use '--l2.genesis' to supply a path to the L2 devnet genesis file. ./bin/cannon run \ --pprof.cpu \ --info-at '%10000000' \ @@ -48,7 +49,7 @@ make cannon --input ./state.bin.gz \ -- \ ../op-program/bin/op-program \ - --network op-mainnet \ + --network \ --l1 \ --l2 \ --l1.head \ diff --git a/cannon/cmd/run.go b/cannon/cmd/run.go index 19352bad7d598..615e913f1e47c 100644 --- a/cannon/cmd/run.go +++ b/cannon/cmd/run.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + mipsexec "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/program" "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" preimage "github.com/ethereum-optimism/optimism/op-preimage" @@ -29,16 +30,16 @@ import ( var ( RunInputFlag = &cli.PathFlag{ Name: "input", - Usage: "path of input JSON state. Stdin if left empty.", + Usage: "path of input binary state. Stdin if left empty.", TakesFile: true, - Value: "state.json", + Value: "state.bin.gz", Required: true, } RunOutputFlag = &cli.PathFlag{ Name: "output", - Usage: "path of output JSON state. Not written if empty, use - to write to Stdout.", + Usage: "path of output binary state. Not written if empty, use - to write to Stdout.", TakesFile: true, - Value: "out.json", + Value: "out.bin.gz", Required: false, } patternHelp = "'never' (default), 'always', '=123' at exactly step 123, '%123' for every 123 steps" @@ -63,7 +64,7 @@ var ( RunSnapshotFmtFlag = &cli.StringFlag{ Name: "snapshot-fmt", Usage: "format for snapshot output file names.", - Value: "state-%d.json", + Value: "state-%d.bin.gz", Required: false, } RunStopAtFlag = &cli.GenericFlag{ @@ -279,6 +280,9 @@ func Run(ctx *cli.Context) error { if ctx.Bool(RunPProfCPU.Name) { defer profile.Start(profile.NoShutdownHook, profile.ProfilePath("."), profile.CPUProfile).Stop() } + if err := checkFlags(ctx); err != nil { + return err + } guestLogger := Logger(os.Stderr, log.LevelInfo) outLog := &mipsevm.LoggingWriter{Log: guestLogger.With("module", "guest", "stream", "stdout")} @@ -407,14 +411,16 @@ func Run(ctx *cli.Context) error { if infoAt(state) { delta := time.Since(start) + pc := state.GetPC() + insn := mipsexec.LoadSubWord(state.GetMemory(), pc, 4, false, new(mipsexec.NoopMemoryTracker)) l.Info("processing", "step", step, "pc", mipsevm.HexU32(state.GetPC()), - "insn", mipsevm.HexU32(state.GetMemory().GetUint32(state.GetPC())), + "insn", mipsevm.HexU32(insn), "ips", float64(step-startStep)/(float64(delta)/float64(time.Second)), "pages", state.GetMemory().PageCount(), "mem", state.GetMemory().Usage(), - "name", meta.LookupSymbol(state.GetPC()), + "name", meta.LookupSymbol(pc), ) } @@ -524,3 +530,17 @@ func CreateRunCommand(action cli.ActionFunc) *cli.Command { } var RunCommand = CreateRunCommand(Run) + +func checkFlags(ctx *cli.Context) error { + if output := ctx.Path(RunOutputFlag.Name); output != "" { + if !serialize.IsBinaryFile(output) { + return errors.New("invalid --output file format. Only binary file formats (ending in .bin or bin.gz) are supported") + } + } + if snapshotFmt := ctx.String(RunSnapshotFmtFlag.Name); snapshotFmt != "" { + if !serialize.IsBinaryFile(fmt.Sprintf(snapshotFmt, 0)) { + return errors.New("invalid --snapshot-fmt file format. Only binary file formats (ending in .bin or bin.gz) are supported") + } + } + return nil +} diff --git a/cannon/mipsevm/README.md b/cannon/mipsevm/README.md index df7558187992b..f01aa61510e54 100644 --- a/cannon/mipsevm/README.md +++ b/cannon/mipsevm/README.md @@ -12,8 +12,10 @@ Supported 63 instructions: | `Conditional Branch` | `beq` | Branch on equal. | | `Conditional Branch` | `bgez` | Branch on greater than or equal to zero. | | `Conditional Branch` | `bgtz` | Branch on greater than zero. | +| `Conditional Branch` | `bgezal` | Branch and link on greater than or equal to zero. | | `Conditional Branch` | `blez` | Branch on less than or equal to zero. | | `Conditional Branch` | `bltz` | Branch on less than zero. | +| `Conditional Branch` | `bltzal` | Branch and link on less than zero. | | `Conditional Branch` | `bne` | Branch on not equal. | | `Logical` | `clo` | Count leading ones. | | `Logical` | `clz` | Count leading zeros. | diff --git a/cannon/mipsevm/arch/arch32.go b/cannon/mipsevm/arch/arch32.go index 87cad3cf504d2..4f9329ef9ce3d 100644 --- a/cannon/mipsevm/arch/arch32.go +++ b/cannon/mipsevm/arch/arch32.go @@ -61,6 +61,7 @@ const ( SysPrlimit64 = 4338 SysClose = 4006 SysPread64 = 4200 + SysStat = 4106 SysFstat = 4108 SysFstat64 = 4215 SysOpenAt = 4288 @@ -79,6 +80,8 @@ const ( SysLlseek = 4140 SysMinCore = 4217 SysTgkill = 4266 + SysGetRLimit = 4076 + SysLseek = 4019 // Profiling-related syscalls SysSetITimer = 4104 SysTimerCreate = 4257 diff --git a/cannon/mipsevm/arch/arch64.go b/cannon/mipsevm/arch/arch64.go index a9b7df70c5830..b6a23f5fde9ef 100644 --- a/cannon/mipsevm/arch/arch64.go +++ b/cannon/mipsevm/arch/arch64.go @@ -25,10 +25,12 @@ const ( AddressMask = 0xFFFFFFFFFFFFFFF8 ExtMask = 0x7 - HeapStart = 0x10_00_00_00_00_00_00_00 - HeapEnd = 0x60_00_00_00_00_00_00_00 - ProgramBreak = 0x40_00_00_00_00_00_00_00 - HighMemoryStart = 0x7F_FF_FF_FF_D0_00_00_00 + // Ensure virtual address is limited to 48-bits as many user programs assume such to implement packed pointers + // limit 0x00_00_FF_FF_FF_FF_FF_FF + HeapStart = 0x00_00_10_00_00_00_00_00 + HeapEnd = 0x00_00_60_00_00_00_00_00 + ProgramBreak = 0x00_00_40_00_00_00_00_00 + HighMemoryStart = 0x00_00_7F_FF_FF_FF_F0_00 ) // MIPS64 syscall table - https://github.com/torvalds/linux/blob/3efc57369a0ce8f76bf0804f7e673982384e4ac9/arch/mips/kernel/syscalls/syscall_n64.tbl. Generate the syscall numbers using the Makefile in that directory. @@ -67,6 +69,7 @@ const ( SysPrlimit64 = 5297 SysClose = 5003 SysPread64 = 5016 + SysStat = 5004 SysFstat = 5005 SysFstat64 = UndefinedSysNr SysOpenAt = 5247 @@ -85,6 +88,8 @@ const ( SysLlseek = UndefinedSysNr SysMinCore = 5026 SysTgkill = 5225 + SysGetRLimit = 5095 + SysLseek = 5008 // Profiling-related syscalls SysSetITimer = 5036 SysTimerCreate = 5216 diff --git a/cannon/mipsevm/exec/calling_convention.go b/cannon/mipsevm/exec/calling_convention.go new file mode 100644 index 0000000000000..8cbb963a96492 --- /dev/null +++ b/cannon/mipsevm/exec/calling_convention.go @@ -0,0 +1,27 @@ +package exec + +// FYI: https://en.wikibooks.org/wiki/MIPS_Assembly/Register_File +// +// https://refspecs.linuxfoundation.org/elf/mipsabi.pdf +const ( + // syscall number; 1st return value + RegV0 = 2 + // syscall arguments; returned unmodified + RegA0 = 4 + RegA1 = 5 + RegA2 = 6 + // 4th syscall argument; set to 0/1 for success/error + RegA3 = 7 +) + +// FYI: https://web.archive.org/web/20231223163047/https://www.linux-mips.org/wiki/Syscall + +const ( + RegSyscallNum = RegV0 + RegSyscallErrno = RegA3 + RegSyscallRet1 = RegV0 + RegSyscallParam1 = RegA0 + RegSyscallParam2 = RegA1 + RegSyscallParam3 = RegA2 + RegSyscallParam4 = RegA3 +) diff --git a/cannon/mipsevm/exec/memory.go b/cannon/mipsevm/exec/memory.go index 1601ad3052282..77c373198ddf0 100644 --- a/cannon/mipsevm/exec/memory.go +++ b/cannon/mipsevm/exec/memory.go @@ -3,6 +3,7 @@ package exec import ( "fmt" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" ) @@ -37,7 +38,7 @@ func (m *MemoryTrackerImpl) TrackMemAccess(effAddr Word) { // TrackMemAccess2 creates a proof for a memory access following a call to TrackMemAccess // This is used to generate proofs for contiguous memory accesses within the same step func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr Word) { - if m.memProofEnabled && m.lastMemAccess+4 != effAddr { + if m.memProofEnabled && m.lastMemAccess+arch.WordSizeBytes != effAddr { panic(fmt.Errorf("unexpected disjointed mem access at %08x, last memory access is at %08x buffered", effAddr, m.lastMemAccess)) } m.lastMemAccess = effAddr @@ -56,3 +57,7 @@ func (m *MemoryTrackerImpl) MemProof() [memory.MemProofSize]byte { func (m *MemoryTrackerImpl) MemProof2() [memory.MemProofSize]byte { return m.memProof2 } + +type NoopMemoryTracker struct{} + +func (n *NoopMemoryTracker) TrackMemAccess(Word) {} diff --git a/cannon/mipsevm/exec/mips_instructions.go b/cannon/mipsevm/exec/mips_instructions.go index a850b8f9d05a5..e0d91893d1a57 100644 --- a/cannon/mipsevm/exec/mips_instructions.go +++ b/cannon/mipsevm/exec/mips_instructions.go @@ -2,13 +2,11 @@ package exec import ( "fmt" + "math/bits" "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" - - // TODO(#12205): MIPS64 port. Replace with a custom library - u128 "lukechampine.com/uint128" ) const ( @@ -16,22 +14,33 @@ const ( OpStoreConditional = 0x38 OpLoadLinked64 = 0x34 OpStoreConditional64 = 0x3c + OpLoadDoubleLeft = 0x1A + OpLoadDoubleRight = 0x1B + + // Return address register + RegRA = 31 ) func GetInstructionDetails(pc Word, memory *memory.Memory) (insn, opcode, fun uint32) { - insn = memory.GetUint32(pc) + if pc&0x3 != 0 { + panic(fmt.Errorf("invalid pc: %x", pc)) + } + word := memory.GetWord(pc & arch.AddressMask) + insn = uint32(SelectSubWord(pc, word, 4, false)) opcode = insn >> 26 // First 6-bits fun = insn & 0x3f // Last 6-bits return insn, opcode, fun } -func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) (memUpdated bool, memAddr Word, err error) { +// ExecMipsCoreStepLogic executes a MIPS instruction that isn't a syscall nor a RMW operation +// If a store operation occurred, then it returns the effective address of the store memory location. +func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) (memUpdated bool, effMemAddr Word, err error) { // j-type j/jal if opcode == 2 || opcode == 3 { linkReg := Word(0) if opcode == 3 { - linkReg = 31 + linkReg = RegRA } // Take the top bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset target := (cpu.NextPC & SignExtend(0xF0000000, 32)) | Word((insn&0x03FFFFFF)<<2) @@ -77,7 +86,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory } if (opcode >= 4 && opcode < 8) || opcode == 1 { - err = HandleBranch(cpu, registers, opcode, insn, rtReg, rs) + err = HandleBranch(cpu, registers, opcode, insn, rtReg, rs, stackTracker) return } @@ -85,7 +94,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory // memory fetch (all I-type) // we do the load for stores also mem := Word(0) - if opcode >= 0x20 { + if opcode >= 0x20 || opcode == OpLoadDoubleLeft || opcode == OpLoadDoubleRight { // M[R[rs]+SignExtImm] rs += SignExtendImmediate(insn) addr := rs & arch.AddressMask @@ -146,7 +155,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory memTracker.TrackMemAccess(storeAddr) memory.SetWord(storeAddr, val) memUpdated = true - memAddr = storeAddr + effMemAddr = storeAddr } // write back the value to destination register @@ -160,7 +169,7 @@ func SignExtendImmediate(insn uint32) Word { func assertMips64(insn uint32) { if arch.IsMips32 { - panic(fmt.Sprintf("invalid instruction: %x", insn)) + panic(fmt.Sprintf("invalid instruction: 0x%08x", insn)) } } @@ -311,14 +320,14 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem case 0x3C: // dsll32 assertMips64(insn) return rt << (((insn >> 6) & 0x1f) + 32) - case 0x3E: // dsll32 + case 0x3E: // dsrl32 assertMips64(insn) return rt >> (((insn >> 6) & 0x1f) + 32) - case 0x3F: // dsll32 + case 0x3F: // dsra32 assertMips64(insn) return Word(int64(rt) >> (((insn >> 6) & 0x1f) + 32)) default: - panic(fmt.Sprintf("invalid instruction: %x", insn)) + panic(fmt.Sprintf("invalid instruction: 0x%08x", insn)) } } else { switch opcode { @@ -340,56 +349,76 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem case 0x0F: // lui return SignExtend(rt<<16, 32) case 0x20: // lb - msb := uint32(arch.WordSize - 8) // 24 for 32-bit and 56 for 64-bit - return SignExtend((mem>>(msb-uint32(rs&arch.ExtMask)*8))&0xFF, 8) + return SelectSubWord(rs, mem, 1, true) case 0x21: // lh - msb := uint32(arch.WordSize - 16) // 16 for 32-bit and 48 for 64-bit - mask := Word(arch.ExtMask - 1) - return SignExtend((mem>>(msb-uint32(rs&mask)*8))&0xFFFF, 16) + return SelectSubWord(rs, mem, 2, true) case 0x22: // lwl - val := mem << ((rs & 3) * 8) - mask := Word(uint32(0xFFFFFFFF) << ((rs & 3) * 8)) - return SignExtend(((rt & ^mask)|val)&0xFFFFFFFF, 32) + if arch.IsMips32 { + val := mem << ((rs & 3) * 8) + mask := Word(uint32(0xFFFFFFFF) << ((rs & 3) * 8)) + return SignExtend(((rt & ^mask)|val)&0xFFFFFFFF, 32) + } else { + // similar to the above mips32 implementation but loads are constrained to the nearest 4-byte memory word + w := uint32(SelectSubWord(rs, mem, 4, false)) + val := w << ((rs & 3) * 8) + mask := Word(uint32(0xFFFFFFFF) << ((rs & 3) * 8)) + return SignExtend(((rt & ^mask)|Word(val))&0xFFFFFFFF, 32) + } case 0x23: // lw - // TODO(#12205): port to MIPS64 - return mem - //return SignExtend((mem>>(32-((rs&0x4)<<3)))&0xFFFFFFFF, 32) + return SelectSubWord(rs, mem, 4, true) case 0x24: // lbu - msb := uint32(arch.WordSize - 8) // 24 for 32-bit and 56 for 64-bit - return (mem >> (msb - uint32(rs&arch.ExtMask)*8)) & 0xFF + return SelectSubWord(rs, mem, 1, false) case 0x25: // lhu - msb := uint32(arch.WordSize - 16) // 16 for 32-bit and 48 for 64-bit - mask := Word(arch.ExtMask - 1) - return (mem >> (msb - uint32(rs&mask)*8)) & 0xFFFF + return SelectSubWord(rs, mem, 2, false) case 0x26: // lwr - val := mem >> (24 - (rs&3)*8) - mask := Word(uint32(0xFFFFFFFF) >> (24 - (rs&3)*8)) - return SignExtend(((rt & ^mask)|val)&0xFFFFFFFF, 32) + if arch.IsMips32 { + val := mem >> (24 - (rs&3)*8) + mask := Word(uint32(0xFFFFFFFF) >> (24 - (rs&3)*8)) + return SignExtend(((rt & ^mask)|val)&0xFFFFFFFF, 32) + } else { + // similar to the above mips32 implementation but constrained to the nearest 4-byte memory word + w := uint32(SelectSubWord(rs, mem, 4, false)) + val := w >> (24 - (rs&3)*8) + mask := uint32(0xFFFFFFFF) >> (24 - (rs&3)*8) + lwrResult := (uint32(rt) & ^mask) | val + if rs&3 == 3 { // loaded bit 31 + return SignExtend(Word(lwrResult), 32) + } else { + // NOTE: cannon64 implementation specific: We leave the upper word untouched + rtMask := uint64(0xFF_FF_FF_FF_00_00_00_00) + return (rt & Word(rtMask)) | Word(lwrResult) + } + } case 0x28: // sb - msb := uint32(arch.WordSize - 8) // 24 for 32-bit and 56 for 64-bit - val := (rt & 0xFF) << (msb - uint32(rs&arch.ExtMask)*8) - mask := ^Word(0) ^ Word(0xFF<<(msb-uint32(rs&arch.ExtMask)*8)) - return (mem & mask) | val + return UpdateSubWord(rs, mem, 1, rt) case 0x29: // sh - msb := uint32(arch.WordSize - 16) // 16 for 32-bit and 48 for 64-bit - rsMask := Word(arch.ExtMask - 1) // 2 for 32-bit and 6 for 64-bit - sl := msb - uint32(rs&rsMask)*8 - val := (rt & 0xFFFF) << sl - mask := ^Word(0) ^ Word(0xFFFF<> ((rs & 3) * 8) - mask := uint32(0xFFFFFFFF) >> ((rs & 3) * 8) - return (mem & Word(^mask)) | val + if arch.IsMips32 { + val := rt >> ((rs & 3) * 8) + mask := uint32(0xFFFFFFFF) >> ((rs & 3) * 8) + return (mem & Word(^mask)) | val + } else { + sr := (rs & 3) << 3 + val := ((rt & 0xFFFFFFFF) >> sr) << (32 - ((rs & 0x4) << 3)) + mask := (uint64(0xFFFFFFFF) >> sr) << (32 - ((rs & 0x4) << 3)) + return (mem & Word(^mask)) | val + } case 0x2b: // sw - // TODO(#12205): port to MIPS64 - return rt + return UpdateSubWord(rs, mem, 4, rt) case 0x2e: // swr - // TODO(#12205): port to MIPS64 - val := rt << (24 - (rs&3)*8) - mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8) - return (mem & Word(^mask)) | val + if arch.IsMips32 { + val := rt << (24 - (rs&3)*8) + mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8) + return (mem & Word(^mask)) | val + } else { + // similar to the above mips32 implementation but constrained to the nearest 4-byte memory word + w := uint32(SelectSubWord(rs, mem, 4, false)) + val := rt << (24 - (rs&3)*8) + mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8) + swrResult := (w & ^mask) | uint32(val) + return UpdateSubWord(rs, mem, 4, Word(swrResult)) + } // MIPS64 case 0x1A: // ldl @@ -424,15 +453,12 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem return mem case 0x3F: // sd assertMips64(insn) - sl := (rs & 0x7) << 3 - val := rt << sl - mask := ^Word(0) << sl - return (mem & ^mask) | val + return rt default: - panic("invalid instruction") + panic(fmt.Sprintf("invalid instruction: %x", insn)) } } - panic("invalid instruction") + panic(fmt.Sprintf("invalid instruction: %x", insn)) } func SignExtend(dat Word, idx Word) Word { @@ -446,12 +472,13 @@ func SignExtend(dat Word, idx Word) Word { } } -func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]Word, opcode uint32, insn uint32, rtReg Word, rs Word) error { +func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]Word, opcode uint32, insn uint32, rtReg Word, rs Word, stackTracker StackTracker) error { if cpu.NextPC != cpu.PC+4 { panic("branch in delay slot") } shouldBranch := false + linked := false if opcode == 4 || opcode == 5 { // beq/bne rt := registers[rtReg] shouldBranch = (rs == rt && opcode == 4) || (rs != rt && opcode == 5) @@ -463,10 +490,20 @@ func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]Word, opcode uint32, i // regimm rtv := (insn >> 16) & 0x1F if rtv == 0 { // bltz - shouldBranch = int32(rs) < 0 + shouldBranch = arch.SignedInteger(rs) < 0 + } + if rtv == 0x10 { // bltzal + shouldBranch = arch.SignedInteger(rs) < 0 + registers[31] = cpu.PC + 8 // always set regardless of branch taken + linked = true } if rtv == 1 { // bgez - shouldBranch = int32(rs) >= 0 + shouldBranch = arch.SignedInteger(rs) >= 0 + } + if rtv == 0x11 { // bgezal (i.e. bal mnemonic) + shouldBranch = arch.SignedInteger(rs) >= 0 + registers[RegRA] = cpu.PC + 8 // always set regardless of branch taken + linked = true } } @@ -474,12 +511,16 @@ func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]Word, opcode uint32, i cpu.PC = cpu.NextPC // execute the delay slot first if shouldBranch { cpu.NextPC = prevPC + 4 + (SignExtend(Word(insn&0xFFFF), 16) << 2) // then continue with the instruction the branch jumps to. + if linked { + stackTracker.PushStack(prevPC, cpu.NextPC) + } } else { cpu.NextPC = cpu.NextPC + 4 // branch not taken } return nil } +// HandleHiLo handles instructions that modify HI and LO registers. It also additionally handles doubleword variable shift operations func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]Word, fun uint32, rs Word, rt Word, storeReg Word) error { val := Word(0) switch fun { @@ -500,9 +541,15 @@ func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]Word, fun uint32, rs Wor cpu.HI = SignExtend(Word(acc>>32), 32) cpu.LO = SignExtend(Word(uint32(acc)), 32) case 0x1a: // div + if uint32(rt) == 0 { + panic("instruction divide by zero") + } cpu.HI = SignExtend(Word(int32(rs)%int32(rt)), 32) cpu.LO = SignExtend(Word(int32(rs)/int32(rt)), 32) case 0x1b: // divu + if uint32(rt) == 0 { + panic("instruction divide by zero") + } cpu.HI = SignExtend(Word(uint32(rs)%uint32(rt)), 32) cpu.LO = SignExtend(Word(uint32(rs)/uint32(rt)), 32) case 0x14: // dsllv @@ -515,22 +562,44 @@ func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]Word, fun uint32, rs Wor assertMips64Fun(fun) val = Word(int64(rt) >> (rs & 0x3F)) case 0x1c: // dmult - // TODO(#12205): port to MIPS64. Is signed multiply needed for dmult assertMips64Fun(fun) - acc := u128.From64(uint64(rs)).Mul(u128.From64(uint64(rt))) - cpu.HI = Word(acc.Hi) - cpu.LO = Word(acc.Lo) + a := int64(rs) + b := int64(rt) + negative := (a < 0) != (b < 0) // set if operands have different signs + + // Handle special case for most negative value to avoid overflow in negation + absA := uint64(abs64(a)) + absB := uint64(abs64(b)) + + hi, lo := bits.Mul64(absA, absB) + if negative { + // Two's complement negation: flip all bits and add 1 + hi = ^hi + lo = ^lo + if lo == 0xFFFFFFFFFFFFFFFF { + hi++ + } + lo++ + } + cpu.HI = Word(hi) + cpu.LO = Word(lo) case 0x1d: // dmultu assertMips64Fun(fun) - acc := u128.From64(uint64(rs)).Mul(u128.From64(uint64(rt))) - cpu.HI = Word(acc.Hi) - cpu.LO = Word(acc.Lo) + hi, lo := bits.Mul64(uint64(rs), uint64(rt)) + cpu.HI = Word(hi) + cpu.LO = Word(lo) case 0x1e: // ddiv assertMips64Fun(fun) + if rt == 0 { + panic("instruction divide by zero") + } cpu.HI = Word(int64(rs) % int64(rt)) cpu.LO = Word(int64(rs) / int64(rt)) case 0x1f: // ddivu assertMips64Fun(fun) + if rt == 0 { + panic("instruction divide by zero") + } cpu.HI = rs % rt cpu.LO = rs / rt } @@ -569,3 +638,66 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]Word, storeReg Word, val W cpu.NextPC = cpu.NextPC + 4 return nil } + +// LoadSubWord loads a subword of byteLength size from memory based on the low-order bits of vaddr +func LoadSubWord(memory *memory.Memory, vaddr Word, byteLength Word, signExtend bool, memoryTracker MemTracker) Word { + // Pull data from memory + effAddr := (vaddr) & arch.AddressMask + memoryTracker.TrackMemAccess(effAddr) + mem := memory.GetWord(effAddr) + + return SelectSubWord(vaddr, mem, byteLength, signExtend) +} + +// StoreSubWord stores a [Word] that has been updated by the specified value at bit positions determined by the vaddr +func StoreSubWord(memory *memory.Memory, vaddr Word, byteLength Word, value Word, memoryTracker MemTracker) { + // Pull data from memory + effAddr := (vaddr) & arch.AddressMask + memoryTracker.TrackMemAccess(effAddr) + mem := memory.GetWord(effAddr) + + // Modify isolated sub-word within mem + newMemVal := UpdateSubWord(vaddr, mem, byteLength, value) + memory.SetWord(effAddr, newMemVal) +} + +// SelectSubWord selects a subword of byteLength size contained in memWord based on the low-order bits of vaddr +// This is the nearest subword that is naturally aligned by the specified byteLength +func SelectSubWord(vaddr Word, memWord Word, byteLength Word, signExtend bool) Word { + // Extract a sub-word based on the low-order bits in vaddr + dataMask, bitOffset, bitLength := calculateSubWordMaskAndOffset(vaddr, byteLength) + retVal := (memWord >> bitOffset) & dataMask + if signExtend { + retVal = SignExtend(retVal, bitLength) + } + return retVal +} + +// UpdateSubWord returns a [Word] that has been updated by the specified value at bit positions determined by the vaddr +func UpdateSubWord(vaddr Word, memWord Word, byteLength Word, value Word) Word { + dataMask, bitOffset, _ := calculateSubWordMaskAndOffset(vaddr, byteLength) + subWordValue := dataMask & value + memUpdateMask := dataMask << bitOffset + return subWordValue<> (arch.WordSize - bitLength) + + // Figure out sub-word index based on the low-order bits in vaddr + byteIndexMask := vaddr & arch.ExtMask & ^(byteLength - 1) + maxByteShift := arch.WordSizeBytes - byteLength + byteIndex := vaddr & byteIndexMask + bitOffset = (maxByteShift - byteIndex) << 3 + + return dataMask, bitOffset, bitLength +} + +// abs64 returns the absolute value +func abs64(x int64) int64 { + if x < 0 { + return -x + } + return x +} diff --git a/cannon/mipsevm/exec/mips_instructions32_test.go b/cannon/mipsevm/exec/mips_instructions32_test.go new file mode 100644 index 0000000000000..689bca6084eba --- /dev/null +++ b/cannon/mipsevm/exec/mips_instructions32_test.go @@ -0,0 +1,110 @@ +// These tests target architectures that are 32-bit or larger +package exec + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" +) + +// TestLoadSubWord_32bits validates LoadSubWord with 32-bit offsets (up to 3 bytes) +func TestLoadSubWord_32bits(t *testing.T) { + cases := []struct { + name string + byteLength Word + addr uint32 + memVal uint32 + signExtend bool + shouldSignExtend bool + expectedValue uint32 + }{ + {name: "32-bit", byteLength: 4, addr: 0xFF00_0000, memVal: 0x1234_5678, expectedValue: 0x1234_5678}, + {name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0001, memVal: 0x1234_5678, expectedValue: 0x1234_5678}, + {name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0002, memVal: 0x1234_5678, expectedValue: 0x1234_5678}, + {name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0003, memVal: 0x1234_5678, expectedValue: 0x1234_5678}, + {name: "16-bit, offset=0", byteLength: 2, addr: 0x00, memVal: 0x1234_5678, expectedValue: 0x1234}, + {name: "16-bit, offset=0, extra bit set", byteLength: 2, addr: 0x01, memVal: 0x1234_5678, expectedValue: 0x1234}, + {name: "16-bit, offset=2", byteLength: 2, addr: 0x02, memVal: 0x1234_5678, expectedValue: 0x5678}, + {name: "16-bit, offset=2, extra bit set", byteLength: 2, addr: 0x03, memVal: 0x1234_5678, expectedValue: 0x5678}, + {name: "16-bit, sign extend positive val", byteLength: 2, addr: 0x02, memVal: 0x1234_5678, expectedValue: 0x5678, signExtend: true, shouldSignExtend: false}, + {name: "16-bit, sign extend negative val", byteLength: 2, addr: 0x02, memVal: 0x1234_F678, expectedValue: 0xFFFF_F678, signExtend: true, shouldSignExtend: true}, + {name: "16-bit, do not sign extend negative val", byteLength: 2, addr: 0x02, memVal: 0x1234_F678, expectedValue: 0xF678, signExtend: false}, + {name: "8-bit, offset=0", byteLength: 1, addr: 0x1230, memVal: 0x1234_5678, expectedValue: 0x12}, + {name: "8-bit, offset=1", byteLength: 1, addr: 0x1231, memVal: 0x1234_5678, expectedValue: 0x34}, + {name: "8-bit, offset=2", byteLength: 1, addr: 0x1232, memVal: 0x1234_5678, expectedValue: 0x56}, + {name: "8-bit, offset=3", byteLength: 1, addr: 0x1233, memVal: 0x1234_5678, expectedValue: 0x78}, + {name: "8-bit, sign extend positive", byteLength: 1, addr: 0x1233, memVal: 0x1234_5678, expectedValue: 0x78, signExtend: true, shouldSignExtend: false}, + {name: "8-bit, sign extend negative", byteLength: 1, addr: 0x1233, memVal: 0x1234_5688, expectedValue: 0xFFFF_FF88, signExtend: true, shouldSignExtend: true}, + {name: "8-bit, do not sign extend neg value", byteLength: 1, addr: 0x1233, memVal: 0x1234_5688, expectedValue: 0x88, signExtend: false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + mem := memory.NewMemory() + memTracker := NewMemoryTracker(mem) + + effAddr := Word(c.addr) & arch.AddressMask + // Shift memval for consistency across architectures + memVal := Word(c.memVal) << (arch.WordSize - 32) + mem.SetWord(effAddr, memVal) + + retVal := LoadSubWord(mem, Word(c.addr), c.byteLength, c.signExtend, memTracker) + + // If sign extending, make sure retVal is consistent across architectures + expected := Word(c.expectedValue) + if c.shouldSignExtend { + signedBits := ^Word(0xFFFF_FFFF) + expected = expected | signedBits + } + require.Equal(t, expected, retVal) + }) + } +} + +// TestStoreSubWord_32bits validates LoadSubWord with 32-bit offsets (up to 3 bytes) +func TestStoreSubWord_32bits(t *testing.T) { + memVal := 0xFFFF_FFFF + value := 0x1234_5678 + + cases := []struct { + name string + byteLength Word + addr uint32 + expectedValue uint32 + }{ + {name: "32-bit", byteLength: 4, addr: 0xFF00_0000, expectedValue: 0x1234_5678}, + {name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0001, expectedValue: 0x1234_5678}, + {name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0002, expectedValue: 0x1234_5678}, + {name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0003, expectedValue: 0x1234_5678}, + {name: "16-bit, subword offset=0", byteLength: 2, addr: 0x00, expectedValue: 0x5678_FFFF}, + {name: "16-bit, subword offset=0, extra bit set", byteLength: 2, addr: 0x01, expectedValue: 0x5678_FFFF}, + {name: "16-bit, subword offset=2", byteLength: 2, addr: 0x02, expectedValue: 0xFFFF_5678}, + {name: "16-bit, subword offset=2, extra bit set", byteLength: 2, addr: 0x03, expectedValue: 0xFFFF_5678}, + {name: "8-bit, offset=0", byteLength: 1, addr: 0x1230, expectedValue: 0x78FF_FFFF}, + {name: "8-bit, offset=1", byteLength: 1, addr: 0x1231, expectedValue: 0xFF78_FFFF}, + {name: "8-bit, offset=2", byteLength: 1, addr: 0x1232, expectedValue: 0xFFFF_78FF}, + {name: "8-bit, offset=3", byteLength: 1, addr: 0x1233, expectedValue: 0xFFFF_FF78}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + mem := memory.NewMemory() + memTracker := NewMemoryTracker(mem) + + effAddr := Word(c.addr) & arch.AddressMask + // Shift memval for consistency across architectures + memVal := Word(memVal) << (arch.WordSize - 32) + mem.SetWord(effAddr, memVal) + + StoreSubWord(mem, Word(c.addr), c.byteLength, Word(value), memTracker) + newMemVal := mem.GetWord(effAddr) + + // Make sure expectation is consistent across architectures + expected := Word(c.expectedValue) << (arch.WordSize - 32) + require.Equal(t, expected, newMemVal) + }) + } +} diff --git a/cannon/mipsevm/exec/mips_instructions64_test.go b/cannon/mipsevm/exec/mips_instructions64_test.go new file mode 100644 index 0000000000000..c293559b6718f --- /dev/null +++ b/cannon/mipsevm/exec/mips_instructions64_test.go @@ -0,0 +1,111 @@ +//go:build cannon64 +// +build cannon64 + +// These tests target architectures that are 64-bit or larger +package exec + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" +) + +// TestLoadSubWord_64bits extends TestLoadSubWord_32bits by testing up to 64-bits (7 byte) offsets +func TestLoadSubWord_64bits(t *testing.T) { + memVal := uint64(0x1234_5678_9876_5432) + cases := []struct { + name string + byteLength Word + addr uint64 + memVal uint64 + signExtend bool + expectedValue uint64 + }{ + {name: "64-bit", byteLength: 8, addr: 0xFF00_0000, memVal: 0x8234_5678_9876_5432, expectedValue: 0x8234_5678_9876_5432}, + {name: "64-bit w sign extension", byteLength: 8, addr: 0xFF00_0000, memVal: 0x8234_5678_9876_5432, expectedValue: 0x8234_5678_9876_5432, signExtend: true}, + {name: "32-bit, offset=0", byteLength: 4, addr: 0xFF00_0000, memVal: memVal, expectedValue: 0x1234_5678}, + {name: "32-bit, offset=0, extra bits", byteLength: 4, addr: 0xFF00_0001, memVal: memVal, expectedValue: 0x1234_5678}, + {name: "32-bit, offset=0, extra bits", byteLength: 4, addr: 0xFF00_0002, memVal: memVal, expectedValue: 0x1234_5678}, + {name: "32-bit, offset=0, extra bits", byteLength: 4, addr: 0xFF00_0003, memVal: memVal, expectedValue: 0x1234_5678}, + {name: "32-bit, offset=4", byteLength: 4, addr: 0xFF00_0004, memVal: memVal, expectedValue: 0x9876_5432}, + {name: "32-bit, offset=4, extra bits", byteLength: 4, addr: 0xFF00_0005, memVal: memVal, expectedValue: 0x9876_5432}, + {name: "32-bit, offset=4, extra bits", byteLength: 4, addr: 0xFF00_0006, memVal: memVal, expectedValue: 0x9876_5432}, + {name: "32-bit, offset=4, extra bits", byteLength: 4, addr: 0xFF00_0007, memVal: memVal, expectedValue: 0x9876_5432}, + {name: "32-bit, sign extend negative", byteLength: 4, addr: 0xFF00_0006, memVal: 0x1234_5678_F1E2_A1B1, expectedValue: 0xFFFF_FFFF_F1E2_A1B1, signExtend: true}, + {name: "32-bit, sign extend positive", byteLength: 4, addr: 0xFF00_0007, memVal: 0x1234_5678_7876_5432, expectedValue: 0x7876_5432, signExtend: true}, + {name: "16-bit, subword offset=4", byteLength: 2, addr: 0x04, memVal: memVal, expectedValue: 0x9876}, + {name: "16-bit, subword offset=4, extra bit set", byteLength: 2, addr: 0x05, memVal: memVal, expectedValue: 0x9876}, + {name: "16-bit, subword offset=6", byteLength: 2, addr: 0x06, memVal: memVal, expectedValue: 0x5432}, + {name: "16-bit, subword offset=6, extra bit set", byteLength: 2, addr: 0x07, memVal: memVal, expectedValue: 0x5432}, + {name: "16-bit, sign extend negative val", byteLength: 2, addr: 0x04, memVal: 0x1234_5678_8BEE_CCDD, expectedValue: 0xFFFF_FFFF_FFFF_8BEE, signExtend: true}, + {name: "16-bit, sign extend positive val", byteLength: 2, addr: 0x04, memVal: 0x1234_5678_7876_5432, expectedValue: 0x7876, signExtend: true}, + {name: "8-bit, offset=4", byteLength: 1, addr: 0x1234, memVal: memVal, expectedValue: 0x98}, + {name: "8-bit, offset=5", byteLength: 1, addr: 0x1235, memVal: memVal, expectedValue: 0x76}, + {name: "8-bit, offset=6", byteLength: 1, addr: 0x1236, memVal: memVal, expectedValue: 0x54}, + {name: "8-bit, offset=7", byteLength: 1, addr: 0x1237, memVal: memVal, expectedValue: 0x32}, + {name: "8-bit, sign extend positive", byteLength: 1, addr: 0x1237, memVal: memVal, expectedValue: 0x32, signExtend: true}, + {name: "8-bit, sign extend negative", byteLength: 1, addr: 0x1237, memVal: 0x1234_5678_8764_4381, expectedValue: 0xFFFF_FFFF_FFFF_FF81, signExtend: true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + mem := memory.NewMemory() + memTracker := NewMemoryTracker(mem) + + effAddr := Word(c.addr) & arch.AddressMask + mem.SetWord(effAddr, c.memVal) + + retVal := LoadSubWord(mem, Word(c.addr), c.byteLength, c.signExtend, memTracker) + require.Equal(t, c.expectedValue, retVal) + }) + } +} + +// TestStoreSubWord_64bits extends TestStoreSubWord_32bits by testing up to 64-bits (7 byte) offsets +func TestStoreSubWord_64bits(t *testing.T) { + memVal := uint64(0xFFFF_FFFF_FFFF_FFFF) + value := uint64(0x1234_5678_9876_5432) + + cases := []struct { + name string + byteLength Word + addr uint64 + expectedValue uint64 + }{ + {name: "64-bit", byteLength: 8, addr: 0xFF00_0000, expectedValue: value}, + {name: "32-bit, offset 0", byteLength: 4, addr: 0xFF00_0000, expectedValue: 0x9876_5432_FFFF_FFFF}, + {name: "32-bit, offset 0, extra addr bits", byteLength: 4, addr: 0xFF00_0001, expectedValue: 0x9876_5432_FFFF_FFFF}, + {name: "32-bit, offset 0, extra addr bits", byteLength: 4, addr: 0xFF00_0002, expectedValue: 0x9876_5432_FFFF_FFFF}, + {name: "32-bit, offset 0, extra addr bits", byteLength: 4, addr: 0xFF00_0003, expectedValue: 0x9876_5432_FFFF_FFFF}, + {name: "32-bit, offset 4", byteLength: 4, addr: 0xFF00_0004, expectedValue: 0xFFFF_FFFF_9876_5432}, + {name: "32-bit, offset 4, extra addr bits", byteLength: 4, addr: 0xFF00_0005, expectedValue: 0xFFFF_FFFF_9876_5432}, + {name: "32-bit, offset 4, extra addr bits", byteLength: 4, addr: 0xFF00_0006, expectedValue: 0xFFFF_FFFF_9876_5432}, + {name: "32-bit, offset 4, extra addr bits", byteLength: 4, addr: 0xFF00_0007, expectedValue: 0xFFFF_FFFF_9876_5432}, + {name: "16-bit, offset=4", byteLength: 2, addr: 0x04, expectedValue: 0xFFFF_FFFF_5432_FFFF}, + {name: "16-bit, offset=4, extra bit set", byteLength: 2, addr: 0x05, expectedValue: 0xFFFF_FFFF_5432_FFFF}, + {name: "16-bit, offset=6", byteLength: 2, addr: 0x06, expectedValue: 0xFFFF_FFFF_FFFF_5432}, + {name: "16-bit, offset=6, extra bit set", byteLength: 2, addr: 0x07, expectedValue: 0xFFFF_FFFF_FFFF_5432}, + {name: "8-bit, offset=4", byteLength: 1, addr: 0x1234, expectedValue: 0xFFFF_FFFF_32FF_FFFF}, + {name: "8-bit, offset=5", byteLength: 1, addr: 0x1235, expectedValue: 0xFFFF_FFFF_FF32_FFFF}, + {name: "8-bit, offset=6", byteLength: 1, addr: 0x1236, expectedValue: 0xFFFF_FFFF_FFFF_32FF}, + {name: "8-bit, offset=7", byteLength: 1, addr: 0x1237, expectedValue: 0xFFFF_FFFF_FFFF_FF32}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + mem := memory.NewMemory() + memTracker := NewMemoryTracker(mem) + + effAddr := Word(c.addr) & arch.AddressMask + mem.SetWord(effAddr, memVal) + + StoreSubWord(mem, Word(c.addr), c.byteLength, Word(value), memTracker) + newMemVal := mem.GetWord(effAddr) + + require.Equal(t, c.expectedValue, newMemVal) + }) + } +} diff --git a/cannon/mipsevm/exec/mips_syscalls.go b/cannon/mipsevm/exec/mips_syscalls.go index 8679a39b773c5..6387a2a2b91f5 100644 --- a/cannon/mipsevm/exec/mips_syscalls.go +++ b/cannon/mipsevm/exec/mips_syscalls.go @@ -99,12 +99,12 @@ const ( ) func GetSyscallArgs(registers *[32]Word) (syscallNum, a0, a1, a2, a3 Word) { - syscallNum = registers[2] // v0 + syscallNum = registers[RegSyscallNum] // v0 - a0 = registers[4] - a1 = registers[5] - a2 = registers[6] - a3 = registers[7] + a0 = registers[RegSyscallParam1] + a1 = registers[RegSyscallParam2] + a2 = registers[RegSyscallParam3] + a3 = registers[RegSyscallParam4] return syscallNum, a0, a1, a2, a3 } @@ -281,8 +281,8 @@ func HandleSysFcntl(a0, a1 Word) (v0, v1 Word) { } func HandleSyscallUpdates(cpu *mipsevm.CpuScalars, registers *[32]Word, v0, v1 Word) { - registers[2] = v0 - registers[7] = v1 + registers[RegSyscallRet1] = v0 + registers[RegSyscallErrno] = v1 cpu.PC = cpu.NextPC cpu.NextPC = cpu.NextPC + 4 diff --git a/cannon/mipsevm/memory/memory.go b/cannon/mipsevm/memory/memory.go index 209d1d431e6f6..1234af4dc4b68 100644 --- a/cannon/mipsevm/memory/memory.go +++ b/cannon/mipsevm/memory/memory.go @@ -192,25 +192,6 @@ func (m *Memory) pageLookup(pageIndex Word) (*CachedPage, bool) { return p, ok } -func (m *Memory) SetUint32(addr Word, v uint32) { - // addr must be aligned to WordSizeBytes bytes - if addr&arch.ExtMask != 0 { - panic(fmt.Errorf("unaligned memory access: %x", addr)) - } - - pageIndex := addr >> PageAddrSize - pageAddr := addr & PageAddrMask - p, ok := m.pageLookup(pageIndex) - if !ok { - // allocate the page if we have not already. - // Go may mmap relatively large ranges, but we only allocate the pages just in time. - p = m.AllocPage(pageIndex) - } else { - m.invalidate(addr) // invalidate this branch of memory, now that the value changed - } - binary.BigEndian.PutUint32(p.Data[pageAddr:pageAddr+4], v) -} - // SetWord stores [arch.Word] sized values at the specified address func (m *Memory) SetWord(addr Word, v Word) { // addr must be aligned to WordSizeBytes bytes @@ -231,20 +212,6 @@ func (m *Memory) SetWord(addr Word, v Word) { arch.ByteOrderWord.PutWord(p.Data[pageAddr:pageAddr+arch.WordSizeBytes], v) } -// GetUint32 returns the first 32 bits located at the specified location. -func (m *Memory) GetUint32(addr Word) uint32 { - // addr must be aligned to 4 bytes - if addr&3 != 0 { - panic(fmt.Errorf("unaligned memory access: %x", addr)) - } - p, ok := m.pageLookup(addr >> PageAddrSize) - if !ok { - return 0 - } - pageAddr := addr & PageAddrMask - return binary.BigEndian.Uint32(p.Data[pageAddr : pageAddr+4]) -} - // GetWord reads the maximum sized value, [arch.Word], located at the specified address. // Note: Also referred to by the MIPS64 specification as a "double-word" memory access. func (m *Memory) GetWord(addr Word) Word { @@ -313,18 +280,22 @@ func (m *Memory) SetMemoryRange(addr Word, r io.Reader) error { for { pageIndex := addr >> PageAddrSize pageAddr := addr & PageAddrMask - p, ok := m.pageLookup(pageIndex) - if !ok { - p = m.AllocPage(pageIndex) - } - p.InvalidateFull() - n, err := r.Read(p.Data[pageAddr:]) + readLen := PageSize - pageAddr + chunk := make([]byte, readLen) + n, err := r.Read(chunk) if err != nil { if err == io.EOF { return nil } return err } + + p, ok := m.pageLookup(pageIndex) + if !ok { + p = m.AllocPage(pageIndex) + } + p.InvalidateFull() + copy(p.Data[pageAddr:], chunk[:n]) addr += Word(n) } } diff --git a/cannon/mipsevm/memory/memory64_test.go b/cannon/mipsevm/memory/memory64_test.go index 3bc3c34677211..203ff5f80d2f4 100644 --- a/cannon/mipsevm/memory/memory64_test.go +++ b/cannon/mipsevm/memory/memory64_test.go @@ -12,6 +12,7 @@ import ( "strings" "testing" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/stretchr/testify/require" ) @@ -140,45 +141,72 @@ func TestMemory64ReadWrite(t *testing.T) { require.Equal(t, make([]byte, 10), res[len(res)-10:], "empty end") }) + t.Run("empty range", func(t *testing.T) { + m := NewMemory() + addr := Word(0xAABBCC00) + r := bytes.NewReader(nil) + pre := m.MerkleRoot() + preJSON, err := m.MarshalJSON() + require.NoError(t, err) + var preSerialized bytes.Buffer + require.NoError(t, m.Serialize(&preSerialized)) + + require.NoError(t, m.SetMemoryRange(addr, r)) + v := m.GetWord(0) + require.Equal(t, Word(0), v) + post := m.MerkleRoot() + require.Equal(t, pre, post) + + // Assert that there are no extra zero pages in serialization + postJSON, err := m.MarshalJSON() + require.NoError(t, err) + require.Equal(t, preJSON, postJSON) + + var postSerialized bytes.Buffer + require.NoError(t, m.Serialize(&postSerialized)) + require.Equal(t, preSerialized.Bytes(), postSerialized.Bytes()) + }) + + t.Run("range page overlap", func(t *testing.T) { + m := NewMemory() + data := bytes.Repeat([]byte{0xAA}, PageAddrSize) + require.NoError(t, m.SetMemoryRange(0, bytes.NewReader(data))) + for i := 0; i < PageAddrSize/arch.WordSizeBytes; i++ { + addr := Word(i * arch.WordSizeBytes) + require.Equal(t, Word(0xAAAAAAAA_AAAAAAAA), m.GetWord(addr)) + } + + data = []byte{0x11, 0x22, 0x33, 0x44} + require.NoError(t, m.SetMemoryRange(0, bytes.NewReader(data))) + require.Equal(t, Word(0x11223344_AAAAAAAA), m.GetWord(0)) + for i := 1; i < PageAddrSize/arch.WordSizeBytes; i++ { + addr := Word(i * arch.WordSizeBytes) + require.Equal(t, Word(0xAAAAAAAA_AAAAAAAA), m.GetWord(addr)) + } + }) + t.Run("read-write", func(t *testing.T) { m := NewMemory() m.SetWord(16, 0xAABBCCDD_EEFF1122) require.Equal(t, Word(0xAABBCCDD_EEFF1122), m.GetWord(16)) - require.Equal(t, uint32(0xAABBCCDD), m.GetUint32(16)) - require.Equal(t, uint32(0xEEFF1122), m.GetUint32(20)) m.SetWord(16, 0xAABB1CDD_EEFF1122) require.Equal(t, Word(0xAABB1CDD_EEFF1122), m.GetWord(16)) - require.Equal(t, uint32(0xAABB1CDD), m.GetUint32(16)) - require.Equal(t, uint32(0xEEFF1122), m.GetUint32(20)) m.SetWord(16, 0xAABB1CDD_EEFF1123) require.Equal(t, Word(0xAABB1CDD_EEFF1123), m.GetWord(16)) - require.Equal(t, uint32(0xAABB1CDD), m.GetUint32(16)) - require.Equal(t, uint32(0xEEFF1123), m.GetUint32(20)) }) t.Run("unaligned read", func(t *testing.T) { m := NewMemory() - m.SetWord(16, 0xAABBCCDD_EEFF1122) + m.SetWord(16, Word(0xAABBCCDD_EEFF1122)) m.SetWord(24, 0x11223344_55667788) for i := Word(17); i < 24; i++ { require.Panics(t, func() { m.GetWord(i) }) - if i != 20 { - require.Panics(t, func() { - m.GetUint32(i) - }) - } } require.Equal(t, Word(0x11223344_55667788), m.GetWord(24)) - require.Equal(t, uint32(0x11223344), m.GetUint32(24)) require.Equal(t, Word(0), m.GetWord(32)) - require.Equal(t, uint32(0), m.GetUint32(32)) require.Equal(t, Word(0xAABBCCDD_EEFF1122), m.GetWord(16)) - require.Equal(t, uint32(0xAABBCCDD), m.GetUint32(16)) - - require.Equal(t, uint32(0xEEFF1122), m.GetUint32(20)) - require.Equal(t, uint32(0x55667788), m.GetUint32(28)) }) t.Run("unaligned write", func(t *testing.T) { @@ -206,7 +234,6 @@ func TestMemory64ReadWrite(t *testing.T) { m.SetWord(23, 0x11223344) }) require.Equal(t, Word(0xAABBCCDD_EEFF1122), m.GetWord(16)) - require.Equal(t, uint32(0xAABBCCDD), m.GetUint32(16)) }) } @@ -218,7 +245,6 @@ func TestMemory64JSON(t *testing.T) { var res Memory require.NoError(t, json.Unmarshal(dat, &res)) require.Equal(t, Word(0xAABBCCDD_EEFF1122), res.GetWord(8)) - require.Equal(t, uint32(0xAABBCCDD), res.GetUint32(8)) } func TestMemory64Copy(t *testing.T) { @@ -226,6 +252,5 @@ func TestMemory64Copy(t *testing.T) { m.SetWord(0xAABBCCDD_8000, 0x000000_AABB) mcpy := m.Copy() require.Equal(t, Word(0xAABB), mcpy.GetWord(0xAABBCCDD_8000)) - require.Equal(t, uint32(0), mcpy.GetUint32(0xAABBCCDD_8000)) require.Equal(t, m.MerkleRoot(), mcpy.MerkleRoot()) } diff --git a/cannon/mipsevm/memory/memory_test.go b/cannon/mipsevm/memory/memory_test.go index 48e6b1d011043..5100de5b41596 100644 --- a/cannon/mipsevm/memory/memory_test.go +++ b/cannon/mipsevm/memory/memory_test.go @@ -12,6 +12,7 @@ import ( "strings" "testing" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/stretchr/testify/require" ) @@ -125,7 +126,6 @@ func TestMemoryReadWrite(t *testing.T) { v := m.GetWord(i) expected := binary.BigEndian.Uint32(data[i : i+4]) require.Equalf(t, expected, v, "read at %d", i) - require.Equalf(t, expected, m.GetUint32(i), "read at %d", i) } }) @@ -140,14 +140,56 @@ func TestMemoryReadWrite(t *testing.T) { require.Equal(t, make([]byte, 10), res[len(res)-10:], "empty end") }) + t.Run("empty range", func(t *testing.T) { + m := NewMemory() + addr := Word(0xAABBCC00) + r := bytes.NewReader(nil) + pre := m.MerkleRoot() + preJSON, err := m.MarshalJSON() + require.NoError(t, err) + var preSerialized bytes.Buffer + require.NoError(t, m.Serialize(&preSerialized)) + + require.NoError(t, m.SetMemoryRange(addr, r)) + v := m.GetWord(0) + require.Equal(t, Word(0), v) + post := m.MerkleRoot() + require.Equal(t, pre, post) + + // Assert that there are no extra zero pages in serialization + postJSON, err := m.MarshalJSON() + require.NoError(t, err) + require.Equal(t, preJSON, postJSON) + + var postSerialized bytes.Buffer + require.NoError(t, m.Serialize(&postSerialized)) + require.Equal(t, preSerialized.Bytes(), postSerialized.Bytes()) + }) + + t.Run("range page overlap", func(t *testing.T) { + m := NewMemory() + data := bytes.Repeat([]byte{0xAA}, PageAddrSize) + require.NoError(t, m.SetMemoryRange(0, bytes.NewReader(data))) + for i := 0; i < PageAddrSize/arch.WordSizeBytes; i++ { + addr := Word(i * arch.WordSizeBytes) + require.Equal(t, Word(0xAAAAAAAA), m.GetWord(addr)) + } + + data = []byte{0x11, 0x22} + require.NoError(t, m.SetMemoryRange(0, bytes.NewReader(data))) + require.Equal(t, Word(0x1122_AAAA), m.GetWord(0)) + for i := 1; i < PageAddrSize/arch.WordSizeBytes; i++ { + addr := Word(i * arch.WordSizeBytes) + require.Equal(t, Word(0xAAAAAAAA), m.GetWord(addr)) + } + }) + t.Run("read-write", func(t *testing.T) { m := NewMemory() m.SetWord(12, 0xAABBCCDD) require.Equal(t, uint32(0xAABBCCDD), m.GetWord(12)) - require.Equal(t, uint32(0xAABBCCDD), m.GetUint32(12)) m.SetWord(12, 0xAABB1CDD) require.Equal(t, uint32(0xAABB1CDD), m.GetWord(12)) - require.Equal(t, uint32(0xAABB1CDD), m.GetUint32(12)) }) t.Run("unaligned read", func(t *testing.T) { @@ -156,22 +198,16 @@ func TestMemoryReadWrite(t *testing.T) { m.SetWord(16, 0x11223344) require.Panics(t, func() { m.GetWord(13) - m.GetUint32(13) }) require.Panics(t, func() { m.GetWord(14) - m.GetUint32(14) }) require.Panics(t, func() { m.GetWord(15) - m.GetUint32(15) }) require.Equal(t, uint32(0x11223344), m.GetWord(16)) - require.Equal(t, uint32(0x11223344), m.GetUint32(16)) require.Equal(t, uint32(0), m.GetWord(20)) - require.Equal(t, uint32(0), m.GetUint32(20)) require.Equal(t, uint32(0xAABBCCDD), m.GetWord(12)) - require.Equal(t, uint32(0xAABBCCDD), m.GetUint32(12)) }) t.Run("unaligned write", func(t *testing.T) { @@ -187,7 +223,6 @@ func TestMemoryReadWrite(t *testing.T) { m.SetWord(15, 0x11223344) }) require.Equal(t, uint32(0xAABBCCDD), m.GetWord(12)) - require.Equal(t, uint32(0xAABBCCDD), m.GetUint32(12)) }) } @@ -199,7 +234,6 @@ func TestMemoryJSON(t *testing.T) { var res Memory require.NoError(t, json.Unmarshal(dat, &res)) require.Equal(t, uint32(0xAABBCCDD), res.GetWord(8)) - require.Equal(t, uint32(0xAABBCCDD), res.GetUint32(8)) } func TestMemoryCopy(t *testing.T) { @@ -207,6 +241,5 @@ func TestMemoryCopy(t *testing.T) { m.SetWord(0x8000, 123) mcpy := m.Copy() require.Equal(t, Word(123), mcpy.GetWord(0x8000)) - require.Equal(t, Word(123), mcpy.GetUint32(0x8000)) require.Equal(t, m.MerkleRoot(), mcpy.MerkleRoot()) } diff --git a/cannon/mipsevm/multithreaded/instrumented_test.go b/cannon/mipsevm/multithreaded/instrumented_test.go index dd4635668458b..659a5b7a5c290 100644 --- a/cannon/mipsevm/multithreaded/instrumented_test.go +++ b/cannon/mipsevm/multithreaded/instrumented_test.go @@ -11,11 +11,12 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/program" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" ) -func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM { - return NewInstrumentedState(state, po, stdOut, stdErr, log, nil) +func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta *program.Metadata) mipsevm.FPVM { + return NewInstrumentedState(state, po, stdOut, stdErr, log, meta) } func TestInstrumentedState_OpenMips(t *testing.T) { @@ -33,29 +34,178 @@ func TestInstrumentedState_Claim(t *testing.T) { testutil.RunVMTest_Claim(t, CreateInitialState, vmFactory, false) } -func TestInstrumentedState_MultithreadedProgram(t *testing.T) { +func TestInstrumentedState_UtilsCheck(t *testing.T) { + // Sanity check that test running utilities will return a non-zero exit code on failure t.Parallel() - state, _ := testutil.LoadELFProgram(t, "../../testdata/example/bin/multithreaded.elf", CreateInitialState, false) - oracle := testutil.StaticOracle(t, []byte{}) - - var stdOutBuf, stdErrBuf bytes.Buffer - us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), nil) - for i := 0; i < 2_000_000; i++ { - if us.GetState().GetExited() { - break - } - _, err := us.Step(false) - require.NoError(t, err) + cases := []struct { + name string + expectedOutput string + }{ + {name: "utilscheck", expectedOutput: "Test failed: ShouldFail"}, + {name: "utilscheck2", expectedOutput: "Test failed: ShouldFail (subtest 2)"}, + {name: "utilscheck3", expectedOutput: "Test panicked: ShouldFail (panic test)"}, + {name: "utilscheck4", expectedOutput: "Test panicked: ShouldFail"}, } - t.Logf("Completed in %d steps", state.Step) - require.True(t, state.Exited, "must complete program") - require.Equal(t, uint8(0), state.ExitCode, "exit with 0") - require.Contains(t, "waitgroup result: 42", stdErrBuf.String()) - require.Contains(t, "channels result: 1234", stdErrBuf.String()) - require.Equal(t, "", stdErrBuf.String(), "should not print any errors") + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + state, meta := testutil.LoadELFProgram(t, testutil.ProgramPath(c.name), CreateInitialState, false) + oracle := testutil.StaticOracle(t, []byte{}) + + var stdOutBuf, stdErrBuf bytes.Buffer + us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta) + + for i := 0; i < 1_000_000; i++ { + if us.GetState().GetExited() { + break + } + _, err := us.Step(false) + require.NoError(t, err) + } + t.Logf("Completed in %d steps", state.Step) + + require.True(t, state.Exited, "must complete program") + require.Equal(t, uint8(1), state.ExitCode, "exit with 1") + require.Contains(t, stdOutBuf.String(), c.expectedOutput) + require.NotContains(t, stdOutBuf.String(), "Passed test that should have failed") + require.Equal(t, "", stdErrBuf.String(), "should not print any errors") + }) + } } +func TestInstrumentedState_MultithreadedProgram(t *testing.T) { + if os.Getenv("SKIP_SLOW_TESTS") == "true" { + t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") + } + + t.Parallel() + cases := []struct { + name string + expectedOutput []string + programName string + steps int + }{ + { + name: "general concurrency test", + expectedOutput: []string{ + "waitgroup result: 42", + "channels result: 1234", + "GC complete!", + }, + programName: "mt-general", + steps: 5_000_000, + }, + { + name: "atomic test", + expectedOutput: []string{ + "Atomic tests passed", + }, + programName: "mt-atomic", + steps: 350_000_000, + }, + { + name: "waitgroup test", + expectedOutput: []string{ + "WaitGroup tests passed", + }, + programName: "mt-wg", + steps: 15_000_000, + }, + { + name: "mutex test", + expectedOutput: []string{ + "Mutex test passed", + }, + programName: "mt-mutex", + steps: 5_000_000, + }, + { + name: "cond test", + expectedOutput: []string{ + "Cond test passed", + }, + programName: "mt-cond", + steps: 5_000_000, + }, + { + name: "rwmutex test", + expectedOutput: []string{ + "RWMutex test passed", + }, + programName: "mt-rwmutex", + steps: 5_000_000, + }, + { + name: "once test", + expectedOutput: []string{ + "Once test passed", + }, + programName: "mt-once", + steps: 5_000_000, + }, + { + name: "oncefunc test", + expectedOutput: []string{ + "OnceFunc tests passed", + }, + programName: "mt-oncefunc", + steps: 15_000_000, + }, + { + name: "map test", + expectedOutput: []string{ + "Map test passed", + }, + programName: "mt-map", + steps: 150_000_000, + }, + { + name: "pool test", + expectedOutput: []string{ + "Pool test passed", + }, + programName: "mt-pool", + steps: 50_000_000, + }, + { + name: "value test", + expectedOutput: []string{ + "Value tests passed", + }, + programName: "mt-value", + steps: 3_000_000, + }, + } + + for _, test := range cases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + state, meta := testutil.LoadELFProgram(t, testutil.ProgramPath(test.programName), CreateInitialState, false) + oracle := testutil.StaticOracle(t, []byte{}) + + var stdOutBuf, stdErrBuf bytes.Buffer + us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta) + + for i := 0; i < test.steps; i++ { + if us.GetState().GetExited() { + break + } + _, err := us.Step(false) + require.NoError(t, err) + } + t.Logf("Completed in %d steps", state.Step) + + require.True(t, state.Exited, "must complete program") + require.Equal(t, uint8(0), state.ExitCode, "exit with 0") + for _, expected := range test.expectedOutput { + require.Contains(t, stdOutBuf.String(), expected) + } + require.Equal(t, "", stdErrBuf.String(), "should not print any errors") + }) + } +} func TestInstrumentedState_Alloc(t *testing.T) { if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") @@ -78,7 +228,7 @@ func TestInstrumentedState_Alloc(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - state, meta := testutil.LoadELFProgram(t, "../../testdata/example/bin/alloc.elf", CreateInitialState, false) + state, meta := testutil.LoadELFProgram(t, testutil.ProgramPath("alloc"), CreateInitialState, false) oracle := testutil.AllocOracle(t, test.numAllocs, test.allocSize) us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), meta) diff --git a/cannon/mipsevm/multithreaded/mips.go b/cannon/mipsevm/multithreaded/mips.go index f738ecf38d71a..b537953a31e61 100644 --- a/cannon/mipsevm/multithreaded/mips.go +++ b/cannon/mipsevm/multithreaded/mips.go @@ -59,8 +59,8 @@ func (m *InstrumentedState) handleSyscall() error { newThread.Registers[29] = a1 // the child will perceive a 0 value as returned value instead, and no error - newThread.Registers[2] = 0 - newThread.Registers[7] = 0 + newThread.Registers[exec.RegSyscallRet1] = 0 + newThread.Registers[exec.RegSyscallErrno] = 0 m.state.NextThreadId++ // Preempt this thread for the new one. But not before updating PCs @@ -168,9 +168,9 @@ func (m *InstrumentedState) handleSyscall() error { m.memoryTracker.TrackMemAccess(effAddr) m.state.Memory.SetWord(effAddr, secs) m.handleMemoryUpdate(effAddr) - m.memoryTracker.TrackMemAccess2(effAddr + 4) - m.state.Memory.SetWord(effAddr+4, nsecs) - m.handleMemoryUpdate(effAddr + 4) + m.memoryTracker.TrackMemAccess2(effAddr + arch.WordSizeBytes) + m.state.Memory.SetWord(effAddr+arch.WordSizeBytes, nsecs) + m.handleMemoryUpdate(effAddr + arch.WordSizeBytes) default: v0 = exec.SysErrorSignal v1 = exec.MipsEINVAL @@ -187,6 +187,7 @@ func (m *InstrumentedState) handleSyscall() error { case arch.SysPrlimit64: case arch.SysClose: case arch.SysPread64: + case arch.SysStat: case arch.SysFstat: case arch.SysOpenAt: case arch.SysReadlink: @@ -206,6 +207,8 @@ func (m *InstrumentedState) handleSyscall() error { case arch.SysTimerCreate: case arch.SysTimerSetTime: case arch.SysTimerDelete: + case arch.SysGetRLimit: + case arch.SysLseek: default: // These syscalls have the same values on 64-bit. So we use if-stmts here to avoid "duplicate case" compiler error for the cannon64 build if arch.IsMips32 && syscallNum == arch.SysFstat64 || syscallNum == arch.SysStat64 || syscallNum == arch.SysLlseek { @@ -221,6 +224,23 @@ func (m *InstrumentedState) handleSyscall() error { } func (m *InstrumentedState) mipsStep() error { + err := m.doMipsStep() + if err != nil { + return err + } + + m.assertPostStateChecks() + return err +} + +func (m *InstrumentedState) assertPostStateChecks() { + activeStack := m.state.getActiveThreadStack() + if len(activeStack) == 0 { + panic("post-state active thread stack is empty") + } +} + +func (m *InstrumentedState) doMipsStep() error { if m.state.Exited { return nil } @@ -313,26 +333,26 @@ func (m *InstrumentedState) mipsStep() error { } // Exec the rest of the step logic - memUpdated, memAddr, err := exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker) + memUpdated, effMemAddr, err := exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker) if err != nil { return err } if memUpdated { - m.handleMemoryUpdate(memAddr) + m.handleMemoryUpdate(effMemAddr) } return nil } -func (m *InstrumentedState) handleMemoryUpdate(memAddr Word) { - if memAddr == m.state.LLAddress { +func (m *InstrumentedState) handleMemoryUpdate(effMemAddr Word) { + if effMemAddr == (arch.AddressMask & m.state.LLAddress) { // Reserved address was modified, clear the reservation m.clearLLMemoryReservation() } } func (m *InstrumentedState) clearLLMemoryReservation() { - m.state.LLReservationActive = false + m.state.LLReservationStatus = LLStatusNone m.state.LLAddress = 0 m.state.LLOwnerThread = 0 } @@ -343,36 +363,40 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error { base := m.state.GetRegistersRef()[baseReg] rtReg := Word((insn >> 16) & 0x1F) offset := exec.SignExtendImmediate(insn) + addr := base + offset - effAddr := (base + offset) & arch.AddressMask - m.memoryTracker.TrackMemAccess(effAddr) - mem := m.state.Memory.GetWord(effAddr) + // Determine some opcode-specific parameters + targetStatus := LLStatusActive32bit + byteLength := Word(4) + if opcode == exec.OpLoadLinked64 || opcode == exec.OpStoreConditional64 { + // Use 64-bit params + targetStatus = LLStatusActive64bit + byteLength = Word(8) + } var retVal Word threadId := m.state.GetCurrentThread().ThreadId - if opcode == exec.OpLoadLinked || opcode == exec.OpLoadLinked64 { - retVal = mem - m.state.LLReservationActive = true - m.state.LLAddress = effAddr + switch opcode { + case exec.OpLoadLinked, exec.OpLoadLinked64: + retVal = exec.LoadSubWord(m.state.GetMemory(), addr, byteLength, true, m.memoryTracker) + + m.state.LLReservationStatus = targetStatus + m.state.LLAddress = addr m.state.LLOwnerThread = threadId - } else if opcode == exec.OpStoreConditional || opcode == exec.OpStoreConditional64 { - // TODO(#12205): Determine bits affected by coherence stores on 64-bits - // Check if our memory reservation is still intact - if m.state.LLReservationActive && m.state.LLOwnerThread == threadId && m.state.LLAddress == effAddr { + case exec.OpStoreConditional, exec.OpStoreConditional64: + if m.state.LLReservationStatus == targetStatus && m.state.LLOwnerThread == threadId && m.state.LLAddress == addr { // Complete atomic update: set memory and return 1 for success m.clearLLMemoryReservation() - rt := m.state.GetRegistersRef()[rtReg] - if opcode == exec.OpStoreConditional { - m.state.Memory.SetUint32(effAddr, uint32(rt)) - } else { - m.state.Memory.SetWord(effAddr, rt) - } + + val := m.state.GetRegistersRef()[rtReg] + exec.StoreSubWord(m.state.GetMemory(), addr, byteLength, val, m.memoryTracker) + retVal = 1 } else { // Atomic update failed, return 0 for failure retVal = 0 } - } else { + default: panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode)) } diff --git a/cannon/mipsevm/multithreaded/state.go b/cannon/mipsevm/multithreaded/state.go index a8a28367408c4..32049e9428b62 100644 --- a/cannon/mipsevm/multithreaded/state.go +++ b/cannon/mipsevm/multithreaded/state.go @@ -40,16 +40,24 @@ const ( STATE_WITNESS_SIZE = THREAD_ID_WITNESS_OFFSET + arch.WordSizeBytes ) +type LLReservationStatus uint8 + +const ( + LLStatusNone LLReservationStatus = 0x0 + LLStatusActive32bit LLReservationStatus = 0x1 + LLStatusActive64bit LLReservationStatus = 0x2 +) + type State struct { Memory *memory.Memory PreimageKey common.Hash PreimageOffset Word // note that the offset includes the 8-byte length prefix - Heap Word // to handle mmap growth - LLReservationActive bool // Whether there is an active memory reservation initiated via the LL (load linked) op - LLAddress Word // The "linked" memory address reserved via the LL (load linked) op - LLOwnerThread Word // The id of the thread that holds the reservation on LLAddress + Heap Word // to handle mmap growth + LLReservationStatus LLReservationStatus // Determines whether there is an active memory reservation, and what type + LLAddress Word // The "linked" memory address reserved via the LL (load linked) op + LLOwnerThread Word // The id of the thread that holds the reservation on LLAddress ExitCode uint8 Exited bool @@ -75,7 +83,7 @@ func CreateEmptyState() *State { return &State{ Memory: memory.NewMemory(), Heap: 0, - LLReservationActive: false, + LLReservationStatus: LLStatusNone, LLAddress: 0, LLOwnerThread: 0, ExitCode: 0, @@ -199,7 +207,7 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) { out = append(out, s.PreimageKey[:]...) out = arch.ByteOrderWord.AppendWord(out, s.PreimageOffset) out = arch.ByteOrderWord.AppendWord(out, s.Heap) - out = mipsevm.AppendBoolToWitness(out, s.LLReservationActive) + out = append(out, byte(s.LLReservationStatus)) out = arch.ByteOrderWord.AppendWord(out, s.LLAddress) out = arch.ByteOrderWord.AppendWord(out, s.LLOwnerThread) out = append(out, s.ExitCode) @@ -278,7 +286,7 @@ func (s *State) Serialize(out io.Writer) error { if err := bout.WriteUInt(s.Heap); err != nil { return err } - if err := bout.WriteBool(s.LLReservationActive); err != nil { + if err := bout.WriteUInt(s.LLReservationStatus); err != nil { return err } if err := bout.WriteUInt(s.LLAddress); err != nil { @@ -347,7 +355,7 @@ func (s *State) Deserialize(in io.Reader) error { if err := bin.ReadUInt(&s.Heap); err != nil { return err } - if err := bin.ReadBool(&s.LLReservationActive); err != nil { + if err := bin.ReadUInt(&s.LLReservationStatus); err != nil { return err } if err := bin.ReadUInt(&s.LLAddress); err != nil { diff --git a/cannon/mipsevm/multithreaded/state_test.go b/cannon/mipsevm/multithreaded/state_test.go index 0beddb75b026e..a4ed646b5325b 100644 --- a/cannon/mipsevm/multithreaded/state_test.go +++ b/cannon/mipsevm/multithreaded/state_test.go @@ -25,6 +25,10 @@ func setWitnessField(witness StateWitness, fieldOffset int, fieldData []byte) { copy(witness[start:end], fieldData) } +func setWitnessWord(witness StateWitness, fieldOffset int, value arch.Word) { + arch.ByteOrderWord.PutWord(witness[fieldOffset:], value) +} + // Run through all permutations of `exited` / `exitCode` and ensure that the // correct witness, state hash, and VM Status is produced. func TestState_EncodeWitness(t *testing.T) { @@ -56,7 +60,7 @@ func TestState_EncodeWitness(t *testing.T) { state.PreimageKey = preimageKey state.PreimageOffset = preimageOffset state.Heap = heap - state.LLReservationActive = true + state.LLReservationStatus = LLStatusActive32bit state.LLAddress = llAddress state.LLOwnerThread = llThreadOwner state.Step = step @@ -70,27 +74,27 @@ func TestState_EncodeWitness(t *testing.T) { expectedWitness := make(StateWitness, STATE_WITNESS_SIZE) setWitnessField(expectedWitness, MEMROOT_WITNESS_OFFSET, memRoot[:]) setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:]) - setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)}) - setWitnessField(expectedWitness, HEAP_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)}) + setWitnessWord(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, preimageOffset) + setWitnessWord(expectedWitness, HEAP_WITNESS_OFFSET, heap) setWitnessField(expectedWitness, LL_RESERVATION_ACTIVE_OFFSET, []byte{1}) - setWitnessField(expectedWitness, LL_ADDRESS_OFFSET, []byte{0, 0, 0, byte(llAddress)}) - setWitnessField(expectedWitness, LL_OWNER_THREAD_OFFSET, []byte{0, 0, 0, byte(llThreadOwner)}) + setWitnessWord(expectedWitness, LL_ADDRESS_OFFSET, llAddress) + setWitnessWord(expectedWitness, LL_OWNER_THREAD_OFFSET, llThreadOwner) setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode}) if c.exited { setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1}) } setWitnessField(expectedWitness, STEP_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)}) setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)}) - setWitnessField(expectedWitness, WAKEUP_WITNESS_OFFSET, []byte{0xFF, 0xFF, 0xFF, 0xFF}) + setWitnessWord(expectedWitness, WAKEUP_WITNESS_OFFSET, ^arch.Word(0)) setWitnessField(expectedWitness, TRAVERSE_RIGHT_WITNESS_OFFSET, []byte{0}) setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:]) setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:]) - setWitnessField(expectedWitness, THREAD_ID_WITNESS_OFFSET, []byte{0, 0, 0, 1}) + setWitnessWord(expectedWitness, THREAD_ID_WITNESS_OFFSET, 1) // Validate witness actualWitness, actualStateHash := state.EncodeWitness() require.Equal(t, len(actualWitness), STATE_WITNESS_SIZE, "Incorrect witness size") - require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness") + require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness: \n\tactual = \t0x%x \n\texpected = \t0x%x", actualWitness, expectedWitness) // Validate witness hash expectedStateHash := crypto.Keccak256Hash(actualWitness) expectedStateHash[0] = mipsevm.VmStatus(c.exited, c.exitCode) @@ -185,7 +189,7 @@ func TestSerializeStateRoundTrip(t *testing.T) { PreimageKey: common.Hash{0xFF}, PreimageOffset: 5, Heap: 0xc0ffee, - LLReservationActive: true, + LLReservationStatus: LLStatusActive64bit, LLAddress: 0x12345678, LLOwnerThread: 0x02, ExitCode: 1, diff --git a/cannon/mipsevm/multithreaded/testutil/expectations.go b/cannon/mipsevm/multithreaded/testutil/expectations.go index fa8d02db37c1b..16971d347cb08 100644 --- a/cannon/mipsevm/multithreaded/testutil/expectations.go +++ b/cannon/mipsevm/multithreaded/testutil/expectations.go @@ -1,6 +1,7 @@ package testutil import ( + "bytes" "fmt" "github.com/ethereum/go-ethereum/common" @@ -10,6 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" ) // ExpectedMTState is a test utility that basically stores a copy of a state that can be explicitly mutated @@ -18,7 +20,7 @@ type ExpectedMTState struct { PreimageKey common.Hash PreimageOffset arch.Word Heap arch.Word - LLReservationActive bool + LLReservationStatus multithreaded.LLReservationStatus LLAddress arch.Word LLOwnerThread arch.Word ExitCode uint8 @@ -69,7 +71,7 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { PreimageKey: fromState.GetPreimageKey(), PreimageOffset: fromState.GetPreimageOffset(), Heap: fromState.GetHeap(), - LLReservationActive: fromState.LLReservationActive, + LLReservationStatus: fromState.LLReservationStatus, LLAddress: fromState.LLAddress, LLOwnerThread: fromState.LLOwnerThread, ExitCode: fromState.GetExitCode(), @@ -119,8 +121,15 @@ func (e *ExpectedMTState) ExpectStep() { e.StepsSinceLastContextSwitch += 1 } -func (e *ExpectedMTState) ExpectMemoryWrite(addr arch.Word, val uint32) { - e.expectedMemory.SetUint32(addr, val) +func (e *ExpectedMTState) ExpectMemoryWriteUint32(t require.TestingT, addr arch.Word, val uint32) { + // Align address to 4-byte boundaries + addr = addr & ^arch.Word(3) + + // Set 4 bytes at addr + data := testutil.Uint32ToBytes(val) + err := e.expectedMemory.SetMemoryRange(addr, bytes.NewReader(data)) + require.NoError(t, err) + e.MemoryRoot = e.expectedMemory.MerkleRoot() } @@ -129,12 +138,6 @@ func (e *ExpectedMTState) ExpectMemoryWordWrite(addr arch.Word, val arch.Word) { e.MemoryRoot = e.expectedMemory.MerkleRoot() } -func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr arch.Word, val uint32, addr2 arch.Word, val2 uint32) { - e.expectedMemory.SetUint32(addr, val) - e.expectedMemory.SetUint32(addr2, val2) - e.MemoryRoot = e.expectedMemory.MerkleRoot() -} - func (e *ExpectedMTState) ExpectPreemption(preState *multithreaded.State) { e.ActiveThreadId = FindNextThread(preState).ThreadId e.StepsSinceLastContextSwitch = 0 @@ -180,7 +183,7 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade require.Equalf(t, e.PreimageKey, actualState.GetPreimageKey(), "Expect preimageKey = %v", e.PreimageKey) require.Equalf(t, e.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset) require.Equalf(t, e.Heap, actualState.GetHeap(), "Expect heap = 0x%x", e.Heap) - require.Equalf(t, e.LLReservationActive, actualState.LLReservationActive, "Expect LLReservationActive = %v", e.LLReservationActive) + require.Equalf(t, e.LLReservationStatus, actualState.LLReservationStatus, "Expect LLReservationStatus = %v", e.LLReservationStatus) require.Equalf(t, e.LLAddress, actualState.LLAddress, "Expect LLAddress = 0x%x", e.LLAddress) require.Equalf(t, e.LLOwnerThread, actualState.LLOwnerThread, "Expect LLOwnerThread = %v", e.LLOwnerThread) require.Equalf(t, e.ExitCode, actualState.GetExitCode(), "Expect exitCode = 0x%x", e.ExitCode) diff --git a/cannon/mipsevm/multithreaded/testutil/expectations_test.go b/cannon/mipsevm/multithreaded/testutil/expectations_test.go index a17534fd5eeab..24bb5ca0f7252 100644 --- a/cannon/mipsevm/multithreaded/testutil/expectations_test.go +++ b/cannon/mipsevm/multithreaded/testutil/expectations_test.go @@ -29,7 +29,7 @@ func TestValidate_shouldCatchMutations(t *testing.T) { {name: "PreimageKey", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageKey = emptyHash }}, {name: "PreimageOffset", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageOffset += 1 }}, {name: "Heap", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Heap += 1 }}, - {name: "LLReservationActive", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLReservationActive = !e.LLReservationActive }}, + {name: "LLReservationStatus", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLReservationStatus = e.LLReservationStatus + 1 }}, {name: "LLAddress", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLAddress += 1 }}, {name: "LLOwnerThread", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLOwnerThread += 1 }}, {name: "ExitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ExitCode += 1 }}, diff --git a/cannon/mipsevm/multithreaded/testutil/mutators.go b/cannon/mipsevm/multithreaded/testutil/mutators.go index 62e22c237c8db..2cd221b8b196d 100644 --- a/cannon/mipsevm/multithreaded/testutil/mutators.go +++ b/cannon/mipsevm/multithreaded/testutil/mutators.go @@ -36,8 +36,8 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) { // Randomize memory-related fields halfMemory := math.MaxUint32 / 2 m.state.Heap = arch.Word(r.Intn(halfMemory) + halfMemory) - m.state.LLReservationActive = r.Intn(2) == 1 - if m.state.LLReservationActive { + m.state.LLReservationStatus = multithreaded.LLReservationStatus(r.Intn(3)) + if m.state.LLReservationStatus != multithreaded.LLStatusNone { m.state.LLAddress = arch.Word(r.Intn(halfMemory)) m.state.LLOwnerThread = arch.Word(r.Intn(10)) } diff --git a/cannon/mipsevm/multithreaded/testutil/thread.go b/cannon/mipsevm/multithreaded/testutil/thread.go index 6cbd3752c613d..79eb71a4a53c3 100644 --- a/cannon/mipsevm/multithreaded/testutil/thread.go +++ b/cannon/mipsevm/multithreaded/testutil/thread.go @@ -21,7 +21,7 @@ func RandomThread(randSeed int64) *multithreaded.ThreadState { return thread } -func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRight bool) { +func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRight bool, opts ...testutil.StateOption) { singleThread := RandomThread(int64(randSeed)) state.NextThreadId = singleThread.ThreadId + 1 @@ -33,6 +33,11 @@ func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRi state.RightThreadStack = []*multithreaded.ThreadState{} state.LeftThreadStack = []*multithreaded.ThreadState{singleThread} } + + mutator := NewStateMutatorMultiThreaded(state) + for _, opt := range opts { + opt(mutator) + } } func SetupThreads(randomSeed int64, state *multithreaded.State, traverseRight bool, activeStackSize, otherStackSize int) { diff --git a/cannon/mipsevm/program/load.go b/cannon/mipsevm/program/load.go index 3cbba07d2bcda..23a1c67b15475 100644 --- a/cannon/mipsevm/program/load.go +++ b/cannon/mipsevm/program/load.go @@ -25,7 +25,7 @@ func LoadELF[T mipsevm.FPVMState](f *elf.File, initState CreateInitialFPVMState[ s := initState(Word(f.Entry), HEAP_START) for i, prog := range f.Progs { - if prog.Type == 0x70000003 { // MIPS_ABIFLAGS + if prog.Type == elf.PT_MIPS_ABIFLAGS { continue } @@ -42,12 +42,27 @@ func LoadELF[T mipsevm.FPVMState](f *elf.File, initState CreateInitialFPVMState[ } } - // TODO(#12205) - if prog.Vaddr+prog.Memsz >= uint64(1<<32) { - return empty, fmt.Errorf("program %d out of 32-bit mem range: %x - %x (size: %x)", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz) + if prog.Memsz == 0 { + // Nothing to do + continue + } + + // Calculate the architecture-specific last valid memory address + var lastMemoryAddr uint64 + if arch.IsMips32 { + // 32-bit virtual address space + lastMemoryAddr = (1 << 32) - 1 + } else { + // 48-bit virtual address space + lastMemoryAddr = (1 << 48) - 1 + } + + lastByteToWrite := prog.Vaddr + prog.Memsz - 1 + if lastByteToWrite > lastMemoryAddr || lastByteToWrite < prog.Vaddr { + return empty, fmt.Errorf("program %d out of memory range: %x - %x (size: %x)", i, prog.Vaddr, lastByteToWrite, prog.Memsz) } - if prog.Vaddr+prog.Memsz >= HEAP_START { - return empty, fmt.Errorf("program %d overlaps with heap: %x - %x (size: %x). The heap start offset must be reconfigured", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz) + if lastByteToWrite >= HEAP_START { + return empty, fmt.Errorf("program %d overlaps with heap: %x - %x (size: %x). The heap start offset must be reconfigured", i, prog.Vaddr, lastByteToWrite, prog.Memsz) } if err := s.GetMemory().SetMemoryRange(Word(prog.Vaddr), r); err != nil { return empty, fmt.Errorf("failed to read program segment %d: %w", i, err) diff --git a/cannon/mipsevm/program/load_test.go b/cannon/mipsevm/program/load_test.go new file mode 100644 index 0000000000000..32660a7f655cc --- /dev/null +++ b/cannon/mipsevm/program/load_test.go @@ -0,0 +1,83 @@ +package program + +import ( + "debug/elf" + "io" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/program/testutil" +) + +func TestLoadELF(t *testing.T) { + data := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} + dataSize := uint64(len(data)) + lastValidAddr := uint64(HEAP_START - 1) + lastAddr := uint64(^uint32(0)) + if !arch.IsMips32 { + lastAddr = (1 << 48) - 1 + } + + tests := []struct { + name string + progType elf.ProgType + memSize uint64 + fileSize uint64 + vAddr uint64 + expectedErr string + shouldIgnore bool + }{ + {name: "Zero length segment", progType: elf.PT_LOAD, fileSize: 0, memSize: 0, vAddr: 0}, + {name: "Zero length segment, non-zero fileSize", progType: elf.PT_LOAD, fileSize: 2, memSize: 0, vAddr: 0, expectedErr: "file size (2) > mem size (0)"}, + {name: "Loadable segment, fileSize > memSize", progType: elf.PT_LOAD, fileSize: dataSize * 2, memSize: dataSize, vAddr: 0x4000, expectedErr: "file size (16) > mem size (8)"}, + {name: "Loadable segment, fileSize < memSize", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize * 2, vAddr: 0x4000}, + {name: "Loadable segment, fileSize == memSize", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize, vAddr: 0x4000}, + {name: "Loadable segment, segment out-of-range", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize, vAddr: lastAddr - 1, expectedErr: "out of memory range"}, + {name: "Loadable segment, segment just out-of-range", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize, vAddr: lastAddr - dataSize + 2, expectedErr: "out of memory range"}, + {name: "Loadable segment, segment just in-range", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize, vAddr: lastAddr - dataSize + 1, expectedErr: "overlaps with heap"}, + {name: "Loadable segment, segment overlaps heap", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize, vAddr: lastValidAddr - 1, expectedErr: "overlaps with heap"}, + {name: "Loadable segment, segment just overlaps heap", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize, vAddr: lastValidAddr - dataSize + 2, expectedErr: "overlaps with heap"}, + {name: "Loadable segment, segment ends just before heap", progType: elf.PT_LOAD, fileSize: dataSize, memSize: dataSize, vAddr: lastValidAddr - dataSize + 1}, + {name: "MIPS Flags segment, invalid file size", progType: elf.PT_MIPS_ABIFLAGS, fileSize: dataSize * 2, memSize: dataSize, vAddr: 0x4000, shouldIgnore: true}, + {name: "MIPS Flags segment, out-of-range", progType: elf.PT_MIPS_ABIFLAGS, fileSize: dataSize, memSize: dataSize, vAddr: lastAddr, shouldIgnore: true}, + {name: "MIPS Flags segment, overlaps heap", progType: elf.PT_MIPS_ABIFLAGS, fileSize: dataSize, memSize: dataSize, vAddr: lastValidAddr, shouldIgnore: true}, + {name: "Other segment, fileSize > memSize", progType: elf.PT_DYNAMIC, fileSize: dataSize * 2, memSize: dataSize, vAddr: 0x4000, expectedErr: "filling for non PT_LOAD segments is not supported"}, + {name: "Other segment, memSize > fileSize", progType: elf.PT_DYNAMIC, fileSize: dataSize, memSize: dataSize * 2, vAddr: 0x4000, expectedErr: "filling for non PT_LOAD segments is not supported"}, + {name: "Other segment, out-of-range", progType: elf.PT_DYNAMIC, fileSize: dataSize, memSize: dataSize, vAddr: lastAddr, expectedErr: "out of memory range"}, + {name: "Other segment, overlaps heap", progType: elf.PT_DYNAMIC, fileSize: dataSize, memSize: dataSize, vAddr: lastValidAddr, expectedErr: "overlaps with heap"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + prog, reader := testutil.MockProgWithReader(tt.progType, tt.fileSize, tt.memSize, tt.vAddr, data) + progs := []*elf.Prog{prog} + mockFile := testutil.MockELFFile(progs) + state, err := LoadELF(mockFile, testutil.MockCreateInitState) + + if tt.expectedErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedErr) + } else { + require.NoError(t, err) + + if tt.shouldIgnore { + // No data should be read + require.Equal(t, reader.BytesRead, 0) + } else { + // Set up memory validation data + expectedData := make([]byte, tt.memSize) + copy(expectedData, data[:]) + memReader := state.GetMemory().ReadMemoryRange(arch.Word(tt.vAddr), arch.Word(tt.memSize)) + actualData, err := io.ReadAll(memReader) + require.NoError(t, err) + + // Validate data was read into memory + require.Equal(t, reader.BytesRead, int(tt.fileSize)) + require.Equal(t, actualData, expectedData) + } + } + }) + } +} diff --git a/cannon/mipsevm/program/testutil/mocks.go b/cannon/mipsevm/program/testutil/mocks.go new file mode 100644 index 0000000000000..484527710aa52 --- /dev/null +++ b/cannon/mipsevm/program/testutil/mocks.go @@ -0,0 +1,130 @@ +package testutil + +import ( + "debug/elf" + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" +) + +// MockELFFile create a mock ELF file with custom program segments +func MockELFFile(progs []*elf.Prog) *elf.File { + return &elf.File{Progs: progs} +} + +// MockProg sets up a elf.Prog structure for testing +func MockProg(progType elf.ProgType, filesz, memsz, vaddr uint64) *elf.Prog { + return &elf.Prog{ + ProgHeader: elf.ProgHeader{ + Type: progType, + Filesz: filesz, + Memsz: memsz, + Vaddr: vaddr, + }, + } +} + +// MockProgWithReader creates an elf.Prog with a TrackableReaderAt to track reads +func MockProgWithReader(progType elf.ProgType, filesz, memsz, vaddr uint64, data []byte) (*elf.Prog, *TrackableReaderAt) { + reader := &TrackableReaderAt{data: data} + prog := MockProg(progType, filesz, memsz, vaddr) + prog.ReaderAt = io.NewSectionReader(reader, 0, int64(filesz)) + return prog, reader +} + +// TrackableReaderAt tracks the number of bytes read +type TrackableReaderAt struct { + data []byte + BytesRead int +} + +func (r *TrackableReaderAt) ReadAt(p []byte, offset int64) (int, error) { + if offset >= int64(len(r.data)) { + return 0, io.EOF + } + numBytesRead := copy(p, r.data[offset:]) + r.BytesRead += numBytesRead + if numBytesRead < len(p) { + return numBytesRead, io.EOF + } + return numBytesRead, nil +} + +// MockCreateInitState returns a mock FPVMState for testing +func MockCreateInitState(pc, heapStart arch.Word) *MockFPVMState { + return newMockFPVMState() +} + +type MockFPVMState struct { + memory *memory.Memory +} + +var _ mipsevm.FPVMState = (*MockFPVMState)(nil) + +func newMockFPVMState() *MockFPVMState { + mem := memory.NewMemory() + state := MockFPVMState{mem} + return &state +} + +func (m MockFPVMState) Serialize(out io.Writer) error { + panic("not implemented") +} + +func (m MockFPVMState) GetMemory() *memory.Memory { + return m.memory +} + +func (m MockFPVMState) GetHeap() arch.Word { + panic("not implemented") +} + +func (m MockFPVMState) GetPreimageKey() common.Hash { + panic("not implemented") +} + +func (m MockFPVMState) GetPreimageOffset() arch.Word { + panic("not implemented") +} + +func (m MockFPVMState) GetPC() arch.Word { + panic("not implemented") +} + +func (m MockFPVMState) GetCpu() mipsevm.CpuScalars { + panic("not implemented") +} + +func (m MockFPVMState) GetRegistersRef() *[32]arch.Word { + panic("not implemented") +} + +func (m MockFPVMState) GetStep() uint64 { + panic("not implemented") +} + +func (m MockFPVMState) GetExited() bool { + panic("not implemented") +} + +func (m MockFPVMState) GetExitCode() uint8 { + panic("not implemented") +} + +func (m MockFPVMState) GetLastHint() hexutil.Bytes { + panic("not implemented") +} + +func (m MockFPVMState) EncodeWitness() (witness []byte, hash common.Hash) { + panic("not implemented") +} + +func (m MockFPVMState) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM { + panic("not implemented") +} diff --git a/cannon/mipsevm/singlethreaded/instrumented_test.go b/cannon/mipsevm/singlethreaded/instrumented_test.go index 8b4c61bd4e148..f1dfc8fae4dbe 100644 --- a/cannon/mipsevm/singlethreaded/instrumented_test.go +++ b/cannon/mipsevm/singlethreaded/instrumented_test.go @@ -1,3 +1,6 @@ +//go:build !cannon64 +// +build !cannon64 + package singlethreaded import ( @@ -7,10 +10,11 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/cannon/mipsevm" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/program" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" ) -func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM { +func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta *program.Metadata) mipsevm.FPVM { return NewInstrumentedState(state, po, stdOut, stdErr, nil) } diff --git a/cannon/mipsevm/singlethreaded/state_test.go b/cannon/mipsevm/singlethreaded/state_test.go index e0639c3dbeece..bfab4dd279885 100644 --- a/cannon/mipsevm/singlethreaded/state_test.go +++ b/cannon/mipsevm/singlethreaded/state_test.go @@ -1,3 +1,6 @@ +//go:build !cannon64 +// +build !cannon64 + package singlethreaded import ( diff --git a/cannon/mipsevm/tests/evm_common64_test.go b/cannon/mipsevm/tests/evm_common64_test.go new file mode 100644 index 0000000000000..ce12538867422 --- /dev/null +++ b/cannon/mipsevm/tests/evm_common64_test.go @@ -0,0 +1,507 @@ +//go:build cannon64 +// +build cannon64 + +package tests + +import ( + "fmt" + "os" + "testing" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" + "github.com/stretchr/testify/require" +) + +func TestEVM_SingleStep_Operators64(t *testing.T) { + cases := []operatorTestCase{ + {name: "dadd. both unsigned 32", funct: 0x2c, isImm: false, rs: Word(0x12), rt: Word(0x20), expectRes: Word(0x32)}, // dadd t0, s1, s2 + {name: "dadd. unsigned 32 and signed", funct: 0x2c, isImm: false, rs: Word(0x12), rt: Word(^uint32(0)), expectRes: Word(0x1_00_00_00_11)}, // dadd t0, s1, s2 + {name: "dadd. signed and unsigned 32", funct: 0x2c, isImm: false, rs: Word(^uint32(0)), rt: Word(0x12), expectRes: Word(0x1_00_00_00_11)}, // dadd t0, s1, s2 + {name: "dadd. unsigned 64 and unsigned 32", funct: 0x2c, isImm: false, rs: Word(0x0FFFFFFF_00000012), rt: Word(0x20), expectRes: Word(0x0FFFFFFF_00000032)}, // dadd t0, s1, s2 + {name: "dadd. unsigned 32 and signed", funct: 0x2c, isImm: false, rs: Word(12), rt: ^Word(0), expectRes: Word(11)}, // dadd t0, s1, s2 + {name: "dadd. signed and unsigned 32", funct: 0x2c, isImm: false, rs: ^Word(0), rt: Word(12), expectRes: Word(11)}, // dadd t0, s1, s2 + {name: "dadd. signed and unsigned 32. expect signed", funct: 0x2c, isImm: false, rs: ^Word(20), rt: Word(4), expectRes: ^Word(16)}, // dadd t0, s1, s2 + {name: "dadd. unsigned 32 and signed. expect signed", funct: 0x2c, isImm: false, rs: Word(4), rt: ^Word(20), expectRes: ^Word(16)}, // dadd t0, s1, s2 + {name: "dadd. both signed", funct: 0x2c, isImm: false, rs: ^Word(10), rt: ^Word(4), expectRes: ^Word(15)}, // dadd t0, s1, s2 + {name: "dadd. signed and unsigned 64. expect unsigned", funct: 0x2c, isImm: false, rs: ^Word(0), rt: Word(0x000000FF_00000000), expectRes: Word(0x000000FE_FFFFFFFF)}, // dadd t0, s1, s2 + {name: "dadd. signed and unsigned 64. expect signed", funct: 0x2c, isImm: false, rs: Word(0x80000000_00000000), rt: Word(0x40000000_00000000), expectRes: Word(0xC000000000000000)}, // dadd t0, s1, s2 + + {name: "daddu. both 32", funct: 0x2d, isImm: false, rs: Word(0x12), rt: Word(0x20), expectRes: Word(0x32)}, // daddu t0, s1, s2 + {name: "daddu. 32-bit. expect doubleword-sized", funct: 0x2d, isImm: false, rs: Word(0x12), rt: Word(^uint32(0)), expectRes: Word(0x1_00_00_00_11)}, // daddu t0, s1, s2 + {name: "daddu. 32-bit. expect double-word sized x", funct: 0x2d, isImm: false, rs: Word(^uint32(0)), rt: Word(0x12), expectRes: Word(0x1_00_00_00_11)}, // dadu t0, s1, s2 + {name: "daddu. doubleword-sized, word-sized", funct: 0x2d, isImm: false, rs: Word(0x0FFFFFFF_00000012), rt: Word(0x20), expectRes: Word(0x0FFFFFFF_00000032)}, // dadu t0, s1, s2 + {name: "daddu. overflow. rt sign bit set", funct: 0x2d, isImm: false, rs: Word(12), rt: ^Word(0), expectRes: Word(11)}, // dadu t0, s1, s2 + {name: "daddu. overflow. rs sign bit set", funct: 0x2d, isImm: false, rs: ^Word(0), rt: Word(12), expectRes: Word(11)}, // dadu t0, s1, s2 + {name: "daddu. doubleword-sized and word-sized", funct: 0x2d, isImm: false, rs: ^Word(20), rt: Word(4), expectRes: ^Word(16)}, // dadu t0, s1, s2 + {name: "daddu. word-sized and doubleword-sized", funct: 0x2d, isImm: false, rs: Word(4), rt: ^Word(20), expectRes: ^Word(16)}, // dadu t0, s1, s2 + {name: "daddu. both doubleword-sized. expect overflow", funct: 0x2d, isImm: false, rs: ^Word(10), rt: ^Word(4), expectRes: ^Word(15)}, // dadu t0, s1, s2 + + {name: "daddi word-sized", opcode: 0x18, isImm: true, rs: Word(12), rt: ^Word(0), imm: uint16(20), expectRes: Word(32)}, // daddi t0, s1, s2 + {name: "daddi doubleword-sized", opcode: 0x18, isImm: true, rs: Word(0x00000010_00000000), rt: ^Word(0), imm: uint16(0x20), expectRes: Word(0x00000010_00000020)}, // daddi t0, s1, s2 + {name: "daddi 32-bit sign", opcode: 0x18, isImm: true, rs: Word(0xFF_FF_FF_FF), rt: ^Word(0), imm: uint16(0x20), expectRes: Word(0x01_00_00_00_1F)}, // daddi t0, s1, s2 + {name: "daddi double-word signed", opcode: 0x18, isImm: true, rs: ^Word(0), rt: ^Word(0), imm: uint16(0x20), expectRes: Word(0x1F)}, // daddi t0, s1, s2 + {name: "daddi double-word signed. expect signed", opcode: 0x18, isImm: true, rs: ^Word(0x10), rt: ^Word(0), imm: uint16(0x1), expectRes: ^Word(0xF)}, // daddi t0, s1, s2 + + {name: "daddiu word-sized", opcode: 0x19, isImm: true, rs: Word(4), rt: ^Word(0), imm: uint16(40), expectRes: Word(44)}, // daddiu t0, s1, 40 + {name: "daddiu doubleword-sized", opcode: 0x19, isImm: true, rs: Word(0x00000010_00000000), rt: ^Word(0), imm: uint16(0x20), expectRes: Word(0x00000010_00000020)}, // daddiu t0, s1, 40 + {name: "daddiu 32-bit sign", opcode: 0x19, isImm: true, rs: Word(0xFF_FF_FF_FF), rt: ^Word(0), imm: uint16(0x20), expectRes: Word(0x01_00_00_00_1F)}, // daddiu t0, s1, 40 + {name: "daddiu overflow", opcode: 0x19, isImm: true, rs: ^Word(0), rt: ^Word(0), imm: uint16(0x20), expectRes: Word(0x1F)}, // daddiu t0, s1, s2 + + {name: "dsub. both unsigned 32", funct: 0x2e, isImm: false, rs: Word(0x12), rt: Word(0x1), expectRes: Word(0x11)}, // dsub t0, s1, s2 + {name: "dsub. signed and unsigned 32", funct: 0x2e, isImm: false, rs: ^Word(1), rt: Word(0x1), expectRes: Word(^uint64(2))}, // dsub t0, s1, s2 + {name: "dsub. signed and unsigned 64", funct: 0x2e, isImm: false, rs: ^Word(1), rt: Word(0x00AABBCC_00000000), expectRes: ^Word(0x00AABBCC_00000001)}, // dsub t0, s1, s2 + {name: "dsub. both signed. unsigned result", funct: 0x2e, isImm: false, rs: ^Word(1), rt: ^Word(2), expectRes: Word(1)}, // dsub t0, s1, s2 + {name: "dsub. both signed. signed result", funct: 0x2e, isImm: false, rs: ^Word(2), rt: ^Word(1), expectRes: ^Word(0)}, // dsub t0, s1, s2 + {name: "dsub. signed and zero", funct: 0x2e, isImm: false, rs: ^Word(0), rt: Word(0), expectRes: ^Word(0)}, // dsub t0, s1, s2 + + {name: "dsubu. both unsigned 32", funct: 0x2f, isImm: false, rs: Word(0x12), rt: Word(0x1), expectRes: Word(0x11)}, // dsubu t0, s1, s2 + {name: "dsubu. signed and unsigned 32", funct: 0x2f, isImm: false, rs: ^Word(1), rt: Word(0x1), expectRes: Word(^uint64(2))}, // dsubu t0, s1, s2 + {name: "dsubu. signed and unsigned 64", funct: 0x2f, isImm: false, rs: ^Word(1), rt: Word(0x00AABBCC_00000000), expectRes: ^Word(0x00AABBCC_00000001)}, // dsubu t0, s1, s2 + {name: "dsubu. both signed. unsigned result", funct: 0x2f, isImm: false, rs: ^Word(1), rt: ^Word(2), expectRes: Word(1)}, // dsubu t0, s1, s2 + {name: "dsubu. both signed. signed result", funct: 0x2f, isImm: false, rs: ^Word(2), rt: ^Word(1), expectRes: ^Word(0)}, // dsubu t0, s1, s2 + {name: "dsubu. signed and zero", funct: 0x2f, isImm: false, rs: ^Word(0), rt: Word(0), expectRes: ^Word(0)}, // dsubu t0, s1, s2 + {name: "dsubu. overflow", funct: 0x2f, isImm: false, rs: Word(0x80000000_00000000), rt: Word(0x7FFFFFFF_FFFFFFFF), expectRes: Word(0x00000000_00000001)}, // dsubu t0, s1, s2 + + // dsllv + {name: "dsllv", funct: 0x14, rt: Word(0x20), rs: Word(0), expectRes: Word(0x20)}, + {name: "dsllv", funct: 0x14, rt: Word(0x20), rs: Word(1), expectRes: Word(0x40)}, + {name: "dsllv sign", funct: 0x14, rt: Word(0x80_00_00_00_00_00_00_20), rs: Word(1), expectRes: Word(0x00_00_00_00_00_00_00_40)}, + {name: "dsllv max", funct: 0x14, rt: Word(0xFF_FF_FF_FF_FF_FF_FF_Fe), rs: Word(0x3f), expectRes: Word(0x0)}, + {name: "dsllv max almost clear", funct: 0x14, rt: Word(0x1), rs: Word(0x3f), expectRes: Word(0x80_00_00_00_00_00_00_00)}, + + // dsrlv t0, s1, s2 + {name: "dsrlv", funct: 0x16, rt: Word(0x20), rs: Word(0), expectRes: Word(0x20)}, + {name: "dsrlv", funct: 0x16, rt: Word(0x20), rs: Word(1), expectRes: Word(0x10)}, + {name: "dsrlv sign-extend", funct: 0x16, rt: Word(0x80_00_00_00_00_00_00_20), rs: Word(1), expectRes: Word(0x40_00_00_00_00_00_00_10)}, + {name: "dsrlv max", funct: 0x16, rt: Word(0x7F_FF_00_00_00_00_00_20), rs: Word(0x3f), expectRes: Word(0x0)}, + {name: "dsrlv max sign-extend", funct: 0x16, rt: Word(0x80_00_00_00_00_00_00_20), rs: Word(0x3f), expectRes: Word(0x1)}, + + // dsrav t0, s1, s2 + {name: "dsrav", funct: 0x17, rt: Word(0x20), rs: Word(0), expectRes: Word(0x20)}, + {name: "dsrav", funct: 0x17, rt: Word(0x20), rs: Word(1), expectRes: Word(0x10)}, + {name: "dsrav sign-extend", funct: 0x17, rt: Word(0x80_00_00_00_00_00_00_20), rs: Word(1), expectRes: Word(0xc0_00_00_00_00_00_00_10)}, + {name: "dsrav max", funct: 0x17, rt: Word(0x7F_FF_00_00_00_00_00_20), rs: Word(0x3f), expectRes: Word(0x0)}, + {name: "dsrav max sign-extend", funct: 0x17, rt: Word(0x80_00_00_00_00_00_00_20), rs: Word(0x3f), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_FF)}, + } + testOperators(t, cases, false) +} + +func TestEVM_SingleStep_Bitwise64(t *testing.T) { + cases := []operatorTestCase{ + {name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2 + {name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40 + {name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2 + {name: "ori", opcode: 0xd, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // ori t0, s1, 40 + {name: "xor", funct: 0x26, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1370)}, // xor t0, s1, s2 + {name: "xori", opcode: 0xe, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // xori t0, s1, 40 + {name: "nor", funct: 0x27, isImm: false, rs: Word(0x4b0), rt: Word(0x1ea), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FA_05)}, // nor t0, s1, s2 + {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(0)}, // slt t0, s1, s2 + {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FF_FF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 + {name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 + } + testOperators(t, cases, false) +} + +func TestEVM_SingleStep_Shift64(t *testing.T) { + cases := []struct { + name string + rd Word + rt Word + sa uint32 + funct uint32 + expectRes Word + }{ + {name: "dsll", funct: 0x38, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 0, expectRes: Word(0x1)}, // dsll t8, s2, 0 + {name: "dsll", funct: 0x38, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 1, expectRes: Word(0x2)}, // dsll t8, s2, 1 + {name: "dsll", funct: 0x38, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 31, expectRes: Word(0x80_00_00_00)}, // dsll t8, s2, 31 + {name: "dsll", funct: 0x38, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_00_00_00_00), sa: 1, expectRes: Word(0xFF_FF_FF_FE_00_00_00_00)}, // dsll t8, s2, 1 + {name: "dsll", funct: 0x38, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_00_00_00_00), sa: 31, expectRes: Word(0x80_00_00_00_00_00_00_00)}, // dsll t8, s2, 31 + + {name: "dsrl", funct: 0x3a, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 0, expectRes: Word(0x1)}, // dsrl t8, s2, 0 + {name: "dsrl", funct: 0x3a, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 1, expectRes: Word(0x0)}, // dsrl t8, s2, 1 + {name: "dsrl", funct: 0x3a, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_00_00_00_00), sa: 1, expectRes: Word(0x7F_FF_FF_FF_80_00_00_00)}, // dsrl t8, s2, 1 + {name: "dsrl", funct: 0x3a, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_00_00_00_00), sa: 31, expectRes: Word(0x01_FF_FF_FF_FE)}, // dsrl t8, s2, 31 + + {name: "dsra", funct: 0x3b, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 0, expectRes: Word(0x1)}, // dsra t8, s2, 0 + {name: "dsra", funct: 0x3b, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 1, expectRes: Word(0x0)}, // dsra t8, s2, 1 + {name: "dsra", funct: 0x3b, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_00_00_00_00), sa: 1, expectRes: Word(0xFF_FF_FF_FF_80_00_00_00)}, // dsra t8, s2, 1 + {name: "dsra", funct: 0x3b, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_00_00_00_00), sa: 31, expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_FE)}, // dsra t8, s2, 31 + + {name: "dsll32", funct: 0x3c, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 0, expectRes: Word(0x1_00_00_00_00)}, // dsll32 t8, s2, 0 + {name: "dsll32", funct: 0x3c, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 1, expectRes: Word(0x2_00_00_00_00)}, // dsll32 t8, s2, 1 + {name: "dsll32", funct: 0x3c, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 31, expectRes: Word(0x80_00_00_00_00_00_00_00)}, // dsll32 t8, s2, 31 + {name: "dsll32", funct: 0x3c, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), sa: 1, expectRes: Word(0xFF_FF_FF_FE_00_00_00_00)}, // dsll32 t8, s2, 1 + {name: "dsll32", funct: 0x3c, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), sa: 31, expectRes: Word(0x80_00_00_00_00_00_00_00)}, // dsll32 t8, s2, 31 + + {name: "dsrl32", funct: 0x3e, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 0, expectRes: Word(0x0)}, // dsrl32 t8, s2, 0 + {name: "dsrl32", funct: 0x3e, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 31, expectRes: Word(0x0)}, // dsrl32 t8, s2, 31 + {name: "dsrl32", funct: 0x3e, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), sa: 1, expectRes: Word(0x7F_FF_FF_FF)}, // dsrl32 t8, s2, 1 + {name: "dsrl32", funct: 0x3e, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), sa: 31, expectRes: Word(0x1)}, // dsrl32 t8, s2, 31 + {name: "dsrl32", funct: 0x3e, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1_0000_0000), sa: 0, expectRes: Word(0x1)}, // dsrl32 t8, s2, 0 + {name: "dsrl32", funct: 0x3e, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1_0000_0000), sa: 31, expectRes: Word(0x0)}, // dsrl32 t8, s2, 31 + + {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 0, expectRes: Word(0x0)}, // dsra32 t8, s2, 0 + {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x1), sa: 1, expectRes: Word(0x0)}, // dsra32 t8, s2, 1 + {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF), sa: 0, expectRes: Word(0x0)}, // dsra32 t8, s2, 0 + {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x01_FF_FF_FF_FF), sa: 0, expectRes: Word(0x1)}, // dsra32 t8, s2, 0 + {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), sa: 1, expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_FF)}, // dsra32 t8, s2, 1 + {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0xFF_FF_FF_00_00_00_00_00), sa: 1, expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_80)}, // dsra32 t8, s2, 1 + {name: "dsra32", funct: 0x3f, rd: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), sa: 31, expectRes: Word(0x0)}, // dsra32 t8, s2, 1 + } + + v := GetMultiThreadedTestCase(t) + for i, tt := range cases { + testName := fmt.Sprintf("%v %v", v.Name, tt.name) + t.Run(testName, func(t *testing.T) { + pc := Word(0x0) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(pc)) + state := goVm.GetState() + var insn uint32 + var rtReg uint32 + var rdReg uint32 + rtReg = 18 + rdReg = 8 + insn = rtReg<<16 | rdReg<<11 | tt.sa<<6 | tt.funct + state.GetRegistersRef()[rdReg] = tt.rd + state.GetRegistersRef()[rtReg] = tt.rt + testutil.StoreInstruction(state.GetMemory(), pc, insn) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + expected.Registers[rdReg] = tt.expectRes + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } +} + +func TestEVM_SingleStep_LoadStore64(t *testing.T) { + t1 := Word(0xFF000000_00000108) + + cases := []loadStoreTestCase{ + {name: "lb 0", opcode: uint32(0x20), memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x71)}, // lb $t0, 0($t1) + {name: "lb 1", opcode: uint32(0x20), imm: 1, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x72)}, // lb $t0, 1($t1) + {name: "lb 2", opcode: uint32(0x20), imm: 2, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x73)}, // lb $t0, 2($t1) + {name: "lb 3", opcode: uint32(0x20), imm: 3, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x74)}, // lb $t0, 3($t1) + {name: "lb 4", opcode: uint32(0x20), imm: 4, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x75)}, // lb $t0, 4($t1) + {name: "lb 5", opcode: uint32(0x20), imm: 5, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x76)}, // lb $t0, 5($t1) + {name: "lb 6", opcode: uint32(0x20), imm: 6, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x77)}, // lb $t0, 6($t1) + {name: "lb 7", opcode: uint32(0x20), imm: 7, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x78)}, // lb $t0, 7($t1) + {name: "lb sign-extended 0", opcode: uint32(0x20), memVal: Word(0x81_72_73_74_75_76_77_78), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_81)}, // lb $t0, 0($t1) + {name: "lb sign-extended 1", opcode: uint32(0x20), imm: 1, memVal: Word(0x71_82_73_74_75_76_77_78), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_82)}, // lb $t0, 1($t1) + {name: "lb sign-extended 2", opcode: uint32(0x20), imm: 2, memVal: Word(0x71_72_83_74_75_76_77_78), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_83)}, // lb $t0, 2($t1) + {name: "lb sign-extended 3", opcode: uint32(0x20), imm: 3, memVal: Word(0x71_72_73_84_75_76_77_78), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_84)}, // lb $t0, 3($t1) + {name: "lb sign-extended 4", opcode: uint32(0x20), imm: 4, memVal: Word(0x71_72_73_74_85_76_77_78), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_85)}, // lb $t0, 4($t1) + {name: "lb sign-extended 5", opcode: uint32(0x20), imm: 5, memVal: Word(0x71_72_73_74_75_86_77_78), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_86)}, // lb $t0, 5($t1) + {name: "lb sign-extended 6", opcode: uint32(0x20), imm: 6, memVal: Word(0x71_72_73_74_75_76_87_78), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_87)}, // lb $t0, 6($t1) + {name: "lb sign-extended 7", opcode: uint32(0x20), imm: 7, memVal: Word(0x71_72_73_74_75_76_77_88), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FF_88)}, // lb $t0, 7($t1) + + {name: "lh offset=0", opcode: uint32(0x21), memVal: Word(0x11223344_55667788), expectRes: Word(0x11_22)}, // lhu $t0, 0($t1) + {name: "lh offset=0 sign-extended", opcode: uint32(0x21), memVal: Word(0x81223344_55667788), expectRes: Word(0xFF_FF_FF_FF_FF_FF_81_22)}, // lhu $t0, 0($t1) + {name: "lh offset=2", opcode: uint32(0x21), imm: 2, memVal: Word(0x11223344_55667788), expectRes: Word(0x33_44)}, // lhu $t0, 2($t1) + {name: "lh offset=2 sign-extended", opcode: uint32(0x21), imm: 2, memVal: Word(0x11228344_55667788), expectRes: Word(0xFF_FF_FF_FF_FF_FF_83_44)}, // lhu $t0, 2($t1) + {name: "lh offset=4", opcode: uint32(0x21), imm: 4, memVal: Word(0x11223344_55667788), expectRes: Word(0x55_66)}, // lhu $t0, 4($t1) + {name: "lh offset=4 sign-extended", opcode: uint32(0x21), imm: 4, memVal: Word(0x11223344_85667788), expectRes: Word(0xFF_FF_FF_FF_FF_FF_85_66)}, // lhu $t0, 4($t1) + {name: "lh offset=6", opcode: uint32(0x21), imm: 6, memVal: Word(0x11223344_55661788), expectRes: Word(0x17_88)}, // lhu $t0, 6($t1) + {name: "lh offset=6 sign-extended", opcode: uint32(0x21), imm: 6, memVal: Word(0x11223344_55668788), expectRes: Word(0xFF_FF_FF_FF_FF_FF_87_88)}, // lhu $t0, 6($t1) + + {name: "lw upper", opcode: uint32(0x23), memVal: Word(0x11223344_55667788), expectRes: Word(0x11223344)}, // lw $t0, 0($t1) + {name: "lw upper sign-extended", opcode: uint32(0x23), memVal: Word(0x81223344_55667788), expectRes: Word(0xFFFFFFFF_81223344)}, // lw $t0, 0($t1) + {name: "lw lower", opcode: uint32(0x23), imm: 4, memVal: Word(0x11223344_55667788), expectRes: Word(0x55667788)}, // lw $t0, 4($t1) + {name: "lw lower sign-extended", opcode: uint32(0x23), imm: 4, memVal: Word(0x11223344_85667788), expectRes: Word(0xFFFFFFFF_85667788)}, // lw $t0, 4($t1) + + {name: "lbu 0", opcode: uint32(0x24), memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x71)}, // lbu $t0, 0($t1) + {name: "lbu 1", opcode: uint32(0x24), imm: 1, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x72)}, // lbu $t0, 1($t1) + {name: "lbu 2", opcode: uint32(0x24), imm: 2, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x73)}, // lbu $t0, 2($t1) + {name: "lbu 3", opcode: uint32(0x24), imm: 3, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x74)}, // lbu $t0, 3($t1) + {name: "lbu 4", opcode: uint32(0x24), imm: 4, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x75)}, // lbu $t0, 4($t1) + {name: "lbu 5", opcode: uint32(0x24), imm: 5, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x76)}, // lbu $t0, 5($t1) + {name: "lbu 6", opcode: uint32(0x24), imm: 6, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x77)}, // lbu $t0, 6($t1) + {name: "lbu 7", opcode: uint32(0x24), imm: 7, memVal: Word(0x71_72_73_74_75_76_77_78), expectRes: Word(0x78)}, // lbu $t0, 7($t1) + {name: "lbu sign-extended 0", opcode: uint32(0x24), memVal: Word(0x81_72_73_74_75_76_77_78), expectRes: Word(0x81)}, // lbu $t0, 0($t1) + {name: "lbu sign-extended 1", opcode: uint32(0x24), imm: 1, memVal: Word(0x71_82_73_74_75_76_77_78), expectRes: Word(0x82)}, // lbu $t0, 1($t1) + {name: "lbu sign-extended 2", opcode: uint32(0x24), imm: 2, memVal: Word(0x71_72_83_74_75_76_77_78), expectRes: Word(0x83)}, // lbu $t0, 2($t1) + {name: "lbu sign-extended 3", opcode: uint32(0x24), imm: 3, memVal: Word(0x71_72_73_84_75_76_77_78), expectRes: Word(0x84)}, // lbu $t0, 3($t1) + {name: "lbu sign-extended 4", opcode: uint32(0x24), imm: 4, memVal: Word(0x71_72_73_74_85_76_77_78), expectRes: Word(0x85)}, // lbu $t0, 4($t1) + {name: "lbu sign-extended 5", opcode: uint32(0x24), imm: 5, memVal: Word(0x71_72_73_74_75_86_77_78), expectRes: Word(0x86)}, // lbu $t0, 5($t1) + {name: "lbu sign-extended 6", opcode: uint32(0x24), imm: 6, memVal: Word(0x71_72_73_74_75_76_87_78), expectRes: Word(0x87)}, // lbu $t0, 6($t1) + {name: "lbu sign-extended 7", opcode: uint32(0x24), imm: 7, memVal: Word(0x71_72_73_74_75_76_77_88), expectRes: Word(0x88)}, // lbu $t0, 7($t1) + + {name: "lhu offset=0", opcode: uint32(0x25), memVal: Word(0x11223344_55667788), expectRes: Word(0x11_22)}, // lhu $t0, 0($t1) + {name: "lhu offset=0 zero-extended", opcode: uint32(0x25), memVal: Word(0x81223344_55667788), expectRes: Word(0x81_22)}, // lhu $t0, 0($t1) + {name: "lhu offset=2", opcode: uint32(0x25), imm: 2, memVal: Word(0x11223344_55667788), expectRes: Word(0x33_44)}, // lhu $t0, 2($t1) + {name: "lhu offset=2 zero-extended", opcode: uint32(0x25), imm: 2, memVal: Word(0x11228344_55667788), expectRes: Word(0x83_44)}, // lhu $t0, 2($t1) + {name: "lhu offset=4", opcode: uint32(0x25), imm: 4, memVal: Word(0x11223344_55667788), expectRes: Word(0x55_66)}, // lhu $t0, 4($t1) + {name: "lhu offset=4 zero-extended", opcode: uint32(0x25), imm: 4, memVal: Word(0x11223344_85667788), expectRes: Word(0x85_66)}, // lhu $t0, 4($t1) + {name: "lhu offset=6", opcode: uint32(0x25), imm: 6, memVal: Word(0x11223344_55661788), expectRes: Word(0x17_88)}, // lhu $t0, 6($t1) + {name: "lhu offset=6 zero-extended", opcode: uint32(0x25), imm: 6, memVal: Word(0x11223344_55668788), expectRes: Word(0x87_88)}, // lhu $t0, 6($t1) + + {name: "lwl", opcode: uint32(0x22), rt: Word(0xaa_bb_cc_dd), imm: 4, memVal: Word(0x12_34_56_78), expectRes: Word(0x12_34_56_78)}, // lwl $t0, 4($t1) + {name: "lwl unaligned address", opcode: uint32(0x22), rt: Word(0xaa_bb_cc_dd), imm: 5, memVal: Word(0x12_34_56_78), expectRes: Word(0x34_56_78_dd)}, // lwl $t0, 5($t1) + {name: "lwl offset 0 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_AA_BB_CC_DD)}, // lwl $t0, 0($t1) + {name: "lwl offset 0 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0x7A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x00_00_00_00_7A_BB_CC_DD)}, // lwl $t0, 0($t1) + {name: "lwl offset 1 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_BB_CC_DD_88)}, // lwl $t0, 1($t1) + {name: "lwl offset 1 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_7B_CC_DD_A1_B1_C1_D1), expectRes: Word(0x00_00_00_00_7B_CC_DD_88)}, // lwl $t0, 1($t1) + {name: "lwl offset 2 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_CC_DD_77_88)}, // lwl $t0, 2($t1) + {name: "lwl offset 2 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_7C_DD_A1_B1_C1_D1), expectRes: Word(0x00_00_00_00_7C_DD_77_88)}, // lwl $t0, 2($t1) + {name: "lwl offset 3 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_DD_66_77_88)}, // lwl $t0, 3($t1) + {name: "lwl offset 3 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_7D_A1_B1_C1_D1), expectRes: Word(0x00_00_00_00_7D_66_77_88)}, // lwl $t0, 3($t1) + {name: "lwl offset 4 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_A1_B1_C1_D1)}, // lwl $t0, 4($t1) + {name: "lwl offset 4 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_71_B1_C1_D1), expectRes: Word(0x00_00_00_00_71_B1_C1_D1)}, // lwl $t0, 4($t1) + {name: "lwl offset 5 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_B1_C1_D1_88)}, // lwl $t0, 5($t1) + {name: "lwl offset 5 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_71_C1_D1), expectRes: Word(0x00_00_00_00_71_C1_D1_88)}, // lwl $t0, 5($t1) + {name: "lwl offset 6 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_C1_D1_77_88)}, // lwl $t0, 6($t1) + {name: "lwl offset 6 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_71_D1), expectRes: Word(0x00_00_00_00_71_D1_77_88)}, // lwl $t0, 6($t1) + {name: "lwl offset 7 sign bit 31 set", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_D1_66_77_88)}, // lwl $t0, 7($t1) + {name: "lwl offset 7 sign bit 31 clear", opcode: uint32(0x22), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_71), expectRes: Word(0x00_00_00_00_71_66_77_88)}, // lwl $t0, 7($t1) + + {name: "lwr zero-extended imm 0 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0x7A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_77_7A)}, // lwr $t0, 0($t1) + {name: "lwr zero-extended imm 0 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_77_AA)}, // lwr $t0, 0($t1) + {name: "lwr zero-extended imm 1 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0x7A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_7A_BB)}, // lwr $t0, 1($t1) + {name: "lwr zero-extended imm 1 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_AA_BB)}, // lwr $t0, 1($t1) + {name: "lwr zero-extended imm 2 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0x7A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_7A_BB_CC)}, // lwr $t0, 2($t1) + {name: "lwr zero-extended imm 2 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_AA_BB_CC)}, // lwr $t0, 2($t1) + {name: "lwr sign-extended imm 3 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0x7A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x00_00_00_00_7A_BB_CC_DD)}, // lwr $t0, 3($t1) + {name: "lwr sign-extended imm 3 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_AA_BB_CC_DD)}, // lwr $t0, 3($t1) + {name: "lwr zero-extended imm 4 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_71_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_77_71)}, // lwr $t0, 4($t1) + {name: "lwr zero-extended imm 4 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_85_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_85_66_77_A1)}, // lwr $t0, 4($t1) + {name: "lwr zero-extended imm 5 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_71_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_71_B1)}, // lwr $t0, 5($t1) + {name: "lwr zero-extended imm 5 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_85_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_85_66_A1_B1)}, // lwr $t0, 5($t1) + {name: "lwr zero-extended imm 6 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_71_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_71_B1_C1)}, // lwr $t0, 6($t1) + {name: "lwr zero-extended imm 6 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_85_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_85_A1_B1_C1)}, // lwr $t0, 6($t1) + {name: "lwr sign-extended imm 7 sign bit 31 clear", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_71_B1_C1_D1), expectRes: Word(0x00_00_00_00_71_B1_C1_D1)}, // lwr $t0, 7($t1) + {name: "lwr sign-extended imm 7 sign bit 31 set", opcode: uint32(0x26), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xFF_FF_FF_FF_A1_B1_C1_D1)}, // lwr $t0, 7($t1) + + {name: "sb offset=0", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, expectMemVal: Word(0x88_BB_CC_DD_A1_B1_C1_D1)}, // sb $t0, 0($t1) + {name: "sb offset=1", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, expectMemVal: Word(0xAA_88_CC_DD_A1_B1_C1_D1)}, // sb $t0, 1($t1) + {name: "sb offset=2", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, expectMemVal: Word(0xAA_BB_88_DD_A1_B1_C1_D1)}, // sb $t0, 2($t1) + {name: "sb offset=3", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, expectMemVal: Word(0xAA_BB_CC_88_A1_B1_C1_D1)}, // sb $t0, 3($t1) + {name: "sb offset=4", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, expectMemVal: Word(0xAA_BB_CC_DD_88_B1_C1_D1)}, // sb $t0, 4($t1) + {name: "sb offset=5", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, expectMemVal: Word(0xAA_BB_CC_DD_A1_88_C1_D1)}, // sb $t0, 5($t1) + {name: "sb offset=6", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, expectMemVal: Word(0xAA_BB_CC_DD_A1_B1_88_D1)}, // sb $t0, 6($t1) + {name: "sb offset=7", opcode: uint32(0x28), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, expectMemVal: Word(0xAA_BB_CC_DD_A1_B1_C1_88)}, // sb $t0, 7($t1) + + {name: "sh offset=0", opcode: uint32(0x29), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, expectMemVal: Word(0x77_88_CC_DD_A1_B1_C1_D1)}, // sh $t0, 0($t1) + {name: "sh offset=2", opcode: uint32(0x29), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, expectMemVal: Word(0xAA_BB_77_88_A1_B1_C1_D1)}, // sh $t0, 2($t1) + {name: "sh offset=4", opcode: uint32(0x29), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, expectMemVal: Word(0xAA_BB_CC_DD_77_88_C1_D1)}, // sh $t0, 4($t1) + {name: "sh offset=6", opcode: uint32(0x29), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, expectMemVal: Word(0xAA_BB_CC_DD_A1_B1_77_88)}, // sh $t0, 6($t1) + + {name: "swl offset=0", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 0, expectMemVal: Word(0x55_66_77_88_A1_B1_C1_D1)}, // swl $t0, 0($t1) + {name: "swl offset=1", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 1, expectMemVal: Word(0xAA_55_66_77_A1_B1_C1_D1)}, // swl $t0, 1($t1) + {name: "swl offset=2", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 2, expectMemVal: Word(0xAA_BB_55_66_A1_B1_C1_D1)}, // swl $t0, 2($t1) + {name: "swl offset=3", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 3, expectMemVal: Word(0xAA_BB_CC_55_A1_B1_C1_D1)}, // swl $t0, 3($t1) + {name: "swl offset=4", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 4, expectMemVal: Word(0xAA_BB_CC_DD_55_66_77_88)}, // swl $t0, 4($t1) + {name: "swl offset=5", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 5, expectMemVal: Word(0xAA_BB_CC_DD_A1_55_66_77)}, // swl $t0, 5($t1) + {name: "swl offset=6", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 6, expectMemVal: Word(0xAA_BB_CC_DD_A1_B1_55_66)}, // swl $t0, 6($t1) + {name: "swl offset=7", opcode: uint32(0x2a), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 7, expectMemVal: Word(0xAA_BB_CC_DD_A1_B1_C1_55)}, // swl $t0, 7($t1) + + {name: "sw offset=0", opcode: uint32(0x2b), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 0, expectMemVal: Word(0x55_66_77_88_A1_B1_C1_D1)}, // sw $t0, 0($t1) + {name: "sw offset=4", opcode: uint32(0x2b), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 4, expectMemVal: Word(0xAA_BB_CC_DD_55_66_77_88)}, // sw $t0, 4($t1) + + {name: "swr offset=0", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x88_BB_CC_DD_A1_B1_C1_D1)}, // swr $t0, 0($t1) + {name: "swr offset=1", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x77_88_CC_DD_A1_B1_C1_D1)}, // swr $t0, 1($t1) + {name: "swr offset=2", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x66_77_88_DD_A1_B1_C1_D1)}, // swr $t0, 2($t1) + {name: "swr offset=3", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x55_66_77_88_A1_B1_C1_D1)}, // swr $t0, 3($t1) + {name: "swr offset=4", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0xAA_BB_CC_DD_88_B1_C1_D1)}, // swr $t0, 4($t1) + {name: "swr offset=5", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0xAA_BB_CC_DD_77_88_C1_D1)}, // swr $t0, 5($t1) + {name: "swr offset=6", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0xAA_BB_CC_DD_66_77_88_D1)}, // swr $t0, 6($t1) + {name: "swr offset=7", opcode: uint32(0x2e), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0xAA_BB_CC_DD_55_66_77_88)}, // swr $t0, 7($t1) + + // 64-bit instructions + {name: "ldl offset 0 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xAA_BB_CC_DD_A1_B1_C1_D1)}, // ldl $t0, 0($t1) + {name: "ldl offset 1 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xBB_CC_DD_A1_B1_C1_D1_88)}, // ldl $t0, 1($t1) + {name: "ldl offset 2 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xCC_DD_A1_B1_C1_D1_77_88)}, // ldl $t0, 2($t1) + {name: "ldl offset 3 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xDD_A1_B1_C1_D1_66_77_88)}, // ldl $t0, 3($t1) + {name: "ldl offset 4 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xA1_B1_C1_D1_55_66_77_88)}, // ldl $t0, 4($t1) + {name: "ldl offset 5 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xB1_C1_D1_44_55_66_77_88)}, // ldl $t0, 5($t1) + {name: "ldl offset 6 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xC1_D1_33_44_55_66_77_88)}, // ldl $t0, 6($t1) + {name: "ldl offset 7 sign bit 31 set", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xD1_22_33_44_55_66_77_88)}, // ldl $t0, 7($t1) + {name: "ldl offset 0 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0x7A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x7A_BB_CC_DD_A1_B1_C1_D1)}, // ldl $t0, 0($t1) + {name: "ldl offset 1 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_7B_CC_DD_A1_B1_C1_D1), expectRes: Word(0x7B_CC_DD_A1_B1_C1_D1_88)}, // ldl $t0, 1($t1) + {name: "ldl offset 2 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_7C_DD_A1_B1_C1_D1), expectRes: Word(0x7C_DD_A1_B1_C1_D1_77_88)}, // ldl $t0, 2($t1) + {name: "ldl offset 3 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_7D_A1_B1_C1_D1), expectRes: Word(0x7D_A1_B1_C1_D1_66_77_88)}, // ldl $t0, 3($t1) + {name: "ldl offset 4 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_71_B1_C1_D1), expectRes: Word(0x71_B1_C1_D1_55_66_77_88)}, // ldl $t0, 4($t1) + {name: "ldl offset 5 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_71_C1_D1), expectRes: Word(0x71_C1_D1_44_55_66_77_88)}, // ldl $t0, 5($t1) + {name: "ldl offset 6 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_71_D1), expectRes: Word(0x71_D1_33_44_55_66_77_88)}, // ldl $t0, 6($t1) + {name: "ldl offset 7 sign bit 31 clear", opcode: uint32(0x1A), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_71), expectRes: Word(0x71_22_33_44_55_66_77_88)}, // ldl $t0, 7($t1) + + {name: "ldr offset 0 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_77_3A)}, // ldr $t0, 0($t1) + {name: "ldr offset 1 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_3A_BB)}, // ldr $t0, 1($t1) + {name: "ldr offset 2 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_3A_BB_CC)}, // ldr $t0, 2($t1) + {name: "ldr offset 3 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_3A_BB_CC_DD)}, // ldr $t0, 3($t1) + {name: "ldr offset 4 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_3A_BB_CC_DD_A1)}, // ldr $t0, 4($t1) + {name: "ldr offset 5 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_3A_BB_CC_DD_A1_B1)}, // ldr $t0, 5($t1) + {name: "ldr offset 6 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_3A_BB_CC_DD_A1_B1_C1)}, // ldr $t0, 6($t1) + {name: "ldr offset 7 sign bit clear", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0x3A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x3A_BB_CC_DD_A1_B1_C1_D1)}, // ldr $t0, 7($t1) + {name: "ldr offset 0 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_77_AA)}, // ldr $t0, 0($t1) + {name: "ldr offset 1 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_66_AA_BB)}, // ldr $t0, 1($t1) + {name: "ldr offset 2 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_55_AA_BB_CC)}, // ldr $t0, 2($t1) + {name: "ldr offset 3 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_44_AA_BB_CC_DD)}, // ldr $t0, 3($t1) + {name: "ldr offset 4 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_33_AA_BB_CC_DD_A1)}, // ldr $t0, 4($t1) + {name: "ldr offset 5 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_22_AA_BB_CC_DD_A1_B1)}, // ldr $t0, 5($t1) + {name: "ldr offset 6 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x11_AA_BB_CC_DD_A1_B1_C1)}, // ldr $t0, 6($t1) + {name: "ldr offset 7 sign bit set", opcode: uint32(0x1b), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0xAA_BB_CC_DD_A1_B1_C1_D1)}, // ldr $t0, 7($t1) + + {name: "lwu upper", opcode: uint32(0x27), memVal: Word(0x11223344_55667788), expectRes: Word(0x11223344)}, // lw $t0, 0($t1) + {name: "lwu upper sign", opcode: uint32(0x27), memVal: Word(0x81223344_55667788), expectRes: Word(0x81223344)}, // lw $t0, 0($t1) + {name: "lwu lower", opcode: uint32(0x27), imm: 4, memVal: Word(0x11223344_55667788), expectRes: Word(0x55667788)}, // lw $t0, 4($t1) + {name: "lwu lower sign", opcode: uint32(0x27), imm: 4, memVal: Word(0x11223344_85667788), expectRes: Word(0x85667788)}, // lw $t0, 4($t1) + + {name: "sdl offset=0", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 0, expectMemVal: Word(0x11_22_33_44_55_66_77_88)}, // sdl $t0, 0($t1) + {name: "sdl offset=1", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 1, expectMemVal: Word(0xAA_11_22_33_44_55_66_77)}, // sdl $t0, 1($t1) + {name: "sdl offset=2", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 2, expectMemVal: Word(0xAA_BB_11_22_33_44_55_66)}, // sdl $t0, 2($t1) + {name: "sdl offset=3", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 3, expectMemVal: Word(0xAA_BB_CC_11_22_33_44_55)}, // sdl $t0, 3($t1) + {name: "sdl offset=4", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 4, expectMemVal: Word(0xAA_BB_CC_DD_11_22_33_44)}, // sdl $t0, 4($t1) + {name: "sdl offset=5", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 5, expectMemVal: Word(0xAA_BB_CC_DD_A1_11_22_33)}, // sdl $t0, 5($t1) + {name: "sdl offset=6", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 6, expectMemVal: Word(0xAA_BB_CC_DD_A1_B1_11_22)}, // sdl $t0, 6($t1) + {name: "sdl offset=7", opcode: uint32(0x2c), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), imm: 7, expectMemVal: Word(0xAA_BB_CC_DD_A1_B1_C1_11)}, // sdl $t0, 7($t1) + + {name: "sdr offset=0", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 0, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x88_BB_CC_DD_A1_B1_C1_D1)}, // sdr $t0, 0($t1) + {name: "sdr offset=1", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 1, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x77_88_CC_DD_A1_B1_C1_D1)}, // sdr $t0, 1($t1) + {name: "sdr offset=2", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 2, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x66_77_88_DD_A1_B1_C1_D1)}, // sdr $t0, 2($t1) + {name: "sdr offset=3", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 3, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x55_66_77_88_A1_B1_C1_D1)}, // sdr $t0, 3($t1) + {name: "sdr offset=4", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 4, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x44_55_66_77_88_B1_C1_D1)}, // sdr $t0, 4($t1) + {name: "sdr offset=5", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 5, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x33_44_55_66_77_88_C1_D1)}, // sdr $t0, 5($t1) + {name: "sdr offset=6", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 6, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x22_33_44_55_66_77_88_D1)}, // sdr $t0, 6($t1) + {name: "sdr offset=7", opcode: uint32(0x2d), rt: Word(0x11_22_33_44_55_66_77_88), imm: 7, memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x11_22_33_44_55_66_77_88)}, // sdr $t0, 7($t1) + + {name: "ld", opcode: uint32(0x37), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0x7A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x7A_BB_CC_DD_A1_B1_C1_D1)}, // ld $t0, 0($t1) + {name: "ld signed", opcode: uint32(0x37), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0x8A_BB_CC_DD_A1_B1_C1_D1), expectRes: Word(0x8A_BB_CC_DD_A1_B1_C1_D1)}, // ld $t0, 0($t1) + + {name: "sd", opcode: uint32(0x3f), rt: Word(0x11_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x11_22_33_44_55_66_77_88)}, // sd $t0, 0($t1) + {name: "sd signed", opcode: uint32(0x3f), rt: Word(0x81_22_33_44_55_66_77_88), memVal: Word(0xAA_BB_CC_DD_A1_B1_C1_D1), expectMemVal: Word(0x81_22_33_44_55_66_77_88)}, // sd $t0, 4($t1) + } + // use a fixed base for all tests + for i := range cases { + cases[i].base = t1 + } + testLoadStore(t, cases) +} + +func TestEVM_SingleStep_MulDiv64(t *testing.T) { + cases := []mulDivTestCase{ + // dmult s1, s2 + // expected hi,lo were verified using qemu-mips + {name: "dmult 0", funct: 0x1c, rs: 0, rt: 0, expectLo: 0, expectHi: 0}, + {name: "dmult 1", funct: 0x1c, rs: 1, rt: 1, expectLo: 1, expectHi: 0}, + {name: "dmult 2", funct: 0x1c, rs: 0x01_00_00_00_00, rt: 2, expectLo: 0x02_00_00_00_00, expectHi: 0}, + {name: "dmult 3", funct: 0x1c, rs: 0x01_00_00_00_00_00_00_00, rt: 2, expectLo: 0x02_00_00_00_00_00_00_00, expectHi: 0}, + {name: "dmult 4", funct: 0x1c, rs: 0x40_00_00_00_00_00_00_00, rt: 2, expectLo: 0x80_00_00_00_00_00_00_00, expectHi: 0x0}, + {name: "dmult 5", funct: 0x1c, rs: 0x40_00_00_00_00_00_00_00, rt: 0x1000, expectLo: 0x0, expectHi: 0x4_00}, + {name: "dmult 6", funct: 0x1c, rs: 0x80_00_00_00_00_00_00_00, rt: 0x1000, expectLo: 0x0, expectHi: 0xFF_FF_FF_FF_FF_FF_F8_00}, + {name: "dmult 7", funct: 0x1c, rs: 0x80_00_00_00_00_00_00_00, rt: 0x80_00_00_00_00_00_00_00, expectLo: 0x0, expectHi: 0x40_00_00_00_00_00_00_00}, + {name: "dmult 8", funct: 0x1c, rs: 0x40_00_00_00_00_00_00_01, rt: 0x1000, expectLo: 0x1000, expectHi: 0x4_00}, + {name: "dmult 9", funct: 0x1c, rs: 0x80_00_00_00_00_00_00_80, rt: 0x80_00_00_00_00_00_00_80, expectLo: 0x4000, expectHi: 0x3F_FF_FF_FF_FF_FF_FF_80}, + {name: "dmult 10", funct: 0x1c, rs: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), rt: Word(0x1), expectLo: 0xFF_FF_FF_FF_FF_FF_FF_FF, expectHi: 0xFF_FF_FF_FF_FF_FF_FF_FF}, + {name: "dmult 11", funct: 0x1c, rs: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), expectLo: 0x1, expectHi: Word(0)}, + {name: "dmult 12", funct: 0x1c, rs: Word(0xFF_FF_FF_FF_FF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD_A1_D1_C1_E0), expectLo: 0xFC_FC_FD_0A_8E_20_EB_A0, expectHi: 0x00_00_00_00_00_00_00_0E}, + {name: "dmult 13", funct: 0x1c, rs: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), rt: Word(0xAA_BB_CC_DD_A1_D1_C1_E1), expectLo: 0xD5_44_33_22_5E_2E_3E_1F, expectHi: 0xD5_5D_E6_6E_D0_E8_E0_F0}, + {name: "dmult 14", funct: 0x1c, rs: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), rt: Word(0x8F_FF_FF_FF_FF_FF_FF_FF), expectLo: 0xF0_00_00_00_00_00_00_01, expectHi: 0xC7_FF_FF_FF_FF_FF_FF_FF}, + + // dmultu s1, s2 + {name: "dmultu 0", funct: 0x1d, rs: 0, rt: 0, expectLo: 0, expectHi: 0}, + {name: "dmultu 1", funct: 0x1d, rs: 1, rt: 1, expectLo: 1, expectHi: 0}, + {name: "dmultu 2", funct: 0x1d, rs: 0x01_00_00_00_00, rt: 2, expectLo: 0x02_00_00_00_00, expectHi: 0}, + {name: "dmultu 3", funct: 0x1d, rs: 0x01_00_00_00_00_00_00_00, rt: 2, expectLo: 0x02_00_00_00_00_00_00_00, expectHi: 0}, + {name: "dmultu 4", funct: 0x1d, rs: 0x40_00_00_00_00_00_00_00, rt: 2, expectLo: 0x80_00_00_00_00_00_00_00, expectHi: 0x0}, + {name: "dmultu 5", funct: 0x1d, rs: 0x40_00_00_00_00_00_00_00, rt: 0x1000, expectLo: 0x0, expectHi: 0x4_00}, + {name: "dmultu 6", funct: 0x1d, rs: 0x80_00_00_00_00_00_00_00, rt: 0x1000, expectLo: 0x0, expectHi: 0x8_00}, + {name: "dmultu 7", funct: 0x1d, rs: 0x80_00_00_00_00_00_00_00, rt: 0x80_00_00_00_00_00_00_00, expectLo: 0x0, expectHi: 0x40_00_00_00_00_00_00_00}, + {name: "dmultu 8", funct: 0x1d, rs: 0x40_00_00_00_00_00_00_01, rt: 0x1000, expectLo: 0x1000, expectHi: 0x4_00}, + {name: "dmultu 9", funct: 0x1d, rs: 0x80_00_00_00_00_00_00_80, rt: 0x80_00_00_00_00_00_00_80, expectLo: 0x4000, expectHi: 0x40_00_00_00_00_00_00_80}, + {name: "dmultu 10", funct: 0x1d, rs: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), expectLo: 0x1, expectHi: Word(0xFF_FF_FF_FF_FF_FF_FF_FE)}, + {name: "dmultu 11", funct: 0x1d, rs: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), expectLo: 0x1, expectHi: 0xFF_FF_FF_FF_FF_FF_FF_FE}, + {name: "dmultu 12", funct: 0x1d, rs: Word(0xFF_FF_FF_FF_FF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD_A1_D1_C1_E0), expectLo: 0xFC_FC_FD_0A_8E_20_EB_A0, expectHi: 0xAA_BB_CC_DD_A1_D1_C1_C1}, + {name: "dmultu 13", funct: 0x1d, rs: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), rt: Word(0xAA_BB_CC_DD_A1_D1_C1_E1), expectLo: 0xD5_44_33_22_5E_2E_3E_1F, expectHi: 0x55_5D_E6_6E_D0_E8_E0_EF}, + {name: "dmultu 14", funct: 0x1d, rs: Word(0x7F_FF_FF_FF_FF_FF_FF_FF), rt: Word(0x8F_FF_FF_FF_FF_FF_FF_FF), expectLo: 0xF0_00_00_00_00_00_00_01, expectHi: 0x47_FF_FF_FF_FF_FF_FF_FE}, + + // ddiv rs, rt + {name: "ddiv", funct: 0x1e, rs: 0, rt: 0, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + {name: "ddiv", funct: 0x1e, rs: 1, rt: 0, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + {name: "ddiv", funct: 0x1e, rs: 0xFF_FF_FF_FF_FF_FF_FF_FF, rt: 0, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + {name: "ddiv", funct: 0x1e, rs: 0, rt: 1, expectLo: 0, expectHi: 0}, + {name: "ddiv", funct: 0x1e, rs: 1, rt: 1, expectLo: 1, expectHi: 0}, + {name: "ddiv", funct: 0x1e, rs: 10, rt: 3, expectLo: 3, expectHi: 1}, + {name: "ddiv", funct: 0x1e, rs: 0x7F_FF_FF_FF_00_00_00_00, rt: 2, expectLo: 0x3F_FF_FF_FF_80_00_00_00, expectHi: 0}, + {name: "ddiv", funct: 0x1e, rs: 0xFF_FF_FF_FF_00_00_00_00, rt: 2, expectLo: 0xFF_FF_FF_FF_80_00_00_00, expectHi: 0}, + {name: "ddiv", funct: 0x1e, rs: ^Word(0), rt: ^Word(0), expectLo: 1, expectHi: 0}, + {name: "ddiv", funct: 0x1e, rs: ^Word(0), rt: 2, expectLo: 0, expectHi: ^Word(0)}, + {name: "ddiv", funct: 0x1e, rs: 0x7F_FF_FF_FF_00_00_00_00, rt: ^Word(0), expectLo: 0x80_00_00_01_00_00_00_00, expectHi: 0}, + + // ddivu + {name: "ddivu", funct: 0x1f, rs: 0, rt: 0, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + {name: "ddivu", funct: 0x1f, rs: 1, rt: 0, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + {name: "ddivu", funct: 0x1f, rs: 0xFF_FF_FF_FF_FF_FF_FF_FF, rt: 0, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + {name: "ddivu", funct: 0x1f, rs: 0, rt: 1, expectLo: 0, expectHi: 0}, + {name: "ddivu", funct: 0x1f, rs: 1, rt: 1, expectLo: 1, expectHi: 0}, + {name: "ddivu", funct: 0x1f, rs: 10, rt: 3, expectLo: 3, expectHi: 1}, + {name: "ddivu", funct: 0x1f, rs: 0x7F_FF_FF_FF_00_00_00_00, rt: 2, expectLo: 0x3F_FF_FF_FF_80_00_00_00, expectHi: 0}, + {name: "ddivu", funct: 0x1f, rs: 0xFF_FF_FF_FF_00_00_00_00, rt: 2, expectLo: 0x7F_FF_FF_FF_80_00_00_00, expectHi: 0}, + {name: "ddivu", funct: 0x1f, rs: ^Word(0), rt: ^Word(0), expectLo: 1, expectHi: 0}, + {name: "ddivu", funct: 0x1f, rs: ^Word(0), rt: 2, expectLo: 0x7F_FF_FF_FF_FF_FF_FF_FF, expectHi: 1}, + {name: "ddivu", funct: 0x1f, rs: 0x7F_FF_FF_FF_00_00_00_00, rt: ^Word(0), expectLo: 0, expectHi: 0x7F_FF_FF_FF_00_00_00_00}, + + // a couple div/divu 64-bit edge cases + {name: "div lower word zero", funct: 0x1a, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + {name: "divu lower word zero", funct: 0x1b, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, + } + + testMulDiv(t, cases, false) +} + +func TestEVM_SingleStep_Branch64(t *testing.T) { + t.Parallel() + cases := []branchTestCase{ + // blez + {name: "blez", pc: 0, opcode: 0x6, rs: 0x5, offset: 0x100, expectNextPC: 0x8}, + {name: "blez large rs", pc: 0x10, opcode: 0x6, rs: 0x7F_FF_FF_FF_FF_FF_FF_FF, offset: 0x100, expectNextPC: 0x18}, + {name: "blez zero rs", pc: 0x10, opcode: 0x6, rs: 0x0, offset: 0x100, expectNextPC: 0x414}, + {name: "blez sign rs", pc: 0x10, opcode: 0x6, rs: -1, offset: 0x100, expectNextPC: 0x414}, + {name: "blez rs only sign bit set", pc: 0x10, opcode: 0x6, rs: testutil.ToSignedInteger(0x80_00_00_00_00_00_00_00), offset: 0x100, expectNextPC: 0x414}, + {name: "blez sign-extended offset", pc: 0x10, opcode: 0x6, rs: -1, offset: 0x80_00, expectNextPC: 0xFF_FF_FF_FF_FF_FE_00_14}, + + // bgtz + {name: "bgtz", pc: 0, opcode: 0x7, rs: 0x5, offset: 0x100, expectNextPC: 0x404}, + {name: "bgtz sign-extended offset", pc: 0x10, opcode: 0x7, rs: 0x5, offset: 0x80_00, expectNextPC: 0xFF_FF_FF_FF_FF_FE_00_14}, + {name: "bgtz large rs", pc: 0x10, opcode: 0x7, rs: 0x7F_FF_FF_FF_FF_FF_FF_FF, offset: 0x100, expectNextPC: 0x414}, + {name: "bgtz zero rs", pc: 0x10, opcode: 0x7, rs: 0x0, offset: 0x100, expectNextPC: 0x18}, + {name: "bgtz sign rs", pc: 0x10, opcode: 0x7, rs: -1, offset: 0x100, expectNextPC: 0x18}, + {name: "bgtz rs only sign bit set", pc: 0x10, opcode: 0x7, rs: testutil.ToSignedInteger(0x80_00_00_00_00_00_00_00), offset: 0x100, expectNextPC: 0x18}, + + // bltz t0, $x + {name: "bltz", pc: 0, opcode: 0x1, regimm: 0x0, rs: 0x5, offset: 0x100, expectNextPC: 0x8}, + {name: "bltz large rs", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: 0x7F_FF_FF_FF_FF_FF_FF_FF, offset: 0x100, expectNextPC: 0x18}, + {name: "bltz zero rs", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: 0x0, offset: 0x100, expectNextPC: 0x18}, + {name: "bltz sign rs", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: -1, offset: 0x100, expectNextPC: 0x414}, + {name: "bltz rs only sign bit set", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: testutil.ToSignedInteger(0x80_00_00_00_00_00_00_00), offset: 0x100, expectNextPC: 0x414}, + {name: "bltz sign-extended offset", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: -1, offset: 0x80_00, expectNextPC: 0xFF_FF_FF_FF_FF_FE_00_14}, + {name: "bltz large offset no-sign", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: -1, offset: 0x7F_FF, expectNextPC: 0x2_00_10}, + + // bgez t0, $x + {name: "bgez", pc: 0, opcode: 0x1, regimm: 0x1, rs: 0x5, offset: 0x100, expectNextPC: 0x404}, + {name: "bgez large rs", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 0x7F_FF_FF_FF_FF_FF_FF_FF, offset: 0x100, expectNextPC: 0x414}, + {name: "bgez zero rs", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 0x0, offset: 0x100, expectNextPC: 0x414}, + {name: "bgez branch not taken", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: -1, offset: 0x100, expectNextPC: 0x18}, + {name: "bgez sign-extended offset", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 1, offset: 0x80_00, expectNextPC: 0xFF_FF_FF_FF_FF_FE_00_14}, + {name: "bgez large offset no-sign", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 1, offset: 0x70_00, expectNextPC: 0x1_C0_14}, + {name: "bgez fill bit offset except sign", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 1, offset: 0x7F_FF, expectNextPC: 0x2_00_10}, + + // bgezal t0, $x + {name: "bgezal", pc: 0, opcode: 0x1, regimm: 0x11, rs: 0x5, offset: 0x100, expectNextPC: 0x404, expectLink: true}, + {name: "bgezal large rs", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 0x7F_FF_FF_FF_FF_FF_FF_FF, offset: 0x100, expectNextPC: 0x414, expectLink: true}, + {name: "bgezal zero rs", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 0x0, offset: 0x100, expectNextPC: 0x414, expectLink: true}, + {name: "bgezal branch not taken", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: -1, offset: 0x100, expectNextPC: 0x18, expectLink: true}, + {name: "bgezal sign-extended offset", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x80_00, expectNextPC: 0xFF_FF_FF_FF_FF_FE_00_14, expectLink: true}, + {name: "bgezal large offset no-sign", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x70_00, expectNextPC: 0x1_C0_14, expectLink: true}, + {name: "bgezal fill bit offset except sign", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x7F_FF, expectNextPC: 0x2_00_10, expectLink: true}, + } + + testBranch(t, cases) +} diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index f351ba4733ebd..cac15fb074e10 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -10,7 +10,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/tracing" "github.com/stretchr/testify/require" @@ -21,12 +20,12 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" ) -func TestEVM(t *testing.T) { +func TestEVM_OpenMIPS(t *testing.T) { + testutil.Cannon32OnlyTest(t, "Skipping MIPS32 assembly test vectors for cannon64") + testFiles, err := os.ReadDir("open_mips_tests/test/bin") require.NoError(t, err) - var tracer *tracing.Hooks // no-tracer by default, but test_util.MarkdownTracer - cases := GetMipsVersionTestCases(t) skippedTests := map[string][]string{ "multi-threaded": {"clone.bin"}, @@ -49,11 +48,7 @@ func TestEVM(t *testing.T) { // Short-circuit early for exit_group.bin exitGroup := f.Name() == "exit_group.bin" expectPanic := strings.HasSuffix(f.Name(), "panic.bin") - - evm := testutil.NewMIPSEVM(c.Contracts) - evm.SetTracer(tracer) - evm.SetLocalOracle(oracle) - testutil.LogStepFailureAtCleanup(t, evm) + validator := testutil.NewEvmValidator(t, c.StateHashFn, c.Contracts, testutil.WithLocalOracle(oracle)) fn := path.Join("open_mips_tests/test/bin", f.Name()) programMem, err := os.ReadFile(fn) @@ -86,17 +81,12 @@ func TestEVM(t *testing.T) { if exitGroup && goVm.GetState().GetExited() { break } - insn := state.GetMemory().GetUint32(state.GetPC()) + insn := testutil.GetInstruction(state.GetMemory(), state.GetPC()) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn) stepWitness, err := goVm.Step(true) require.NoError(t, err) - evmPost := evm.Step(t, stepWitness, curStep, c.StateHashFn) - // verify the post-state matches. - // TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison. - goPost, _ := goVm.GetState().EncodeWitness() - require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), - "mipsevm produced different state than EVM at step %d", state.GetStep()) + validator.ValidateEVM(t, stepWitness, curStep, goVm) } if exitGroup { require.NotEqual(t, arch.Word(testutil.EndAddr), goVm.GetState().GetPC(), "must not reach end") @@ -116,9 +106,7 @@ func TestEVM(t *testing.T) { } } -func TestEVMSingleStep_Jump(t *testing.T) { - var tracer *tracing.Hooks - +func TestEVM_SingleStep_Jump(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -140,7 +128,7 @@ func TestEVMSingleStep_Jump(t *testing.T) { t.Run(testName, func(t *testing.T) { goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(tt.pc), testutil.WithNextPC(tt.nextPC)) state := goVm.GetState() - state.GetMemory().SetUint32(tt.pc, tt.insn) + testutil.StoreInstruction(state.GetMemory(), tt.pc, tt.insn) step := state.GetStep() // Setup expectations @@ -157,166 +145,197 @@ func TestEVMSingleStep_Jump(t *testing.T) { // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } } -func TestEVMSingleStep_Operators(t *testing.T) { - var tracer *tracing.Hooks +func TestEVM_SingleStep_Operators(t *testing.T) { + cases := []operatorTestCase{ + {name: "add", funct: 0x20, isImm: false, rs: Word(12), rt: Word(20), expectRes: Word(32)}, // add t0, s1, s2 + {name: "add", funct: 0x20, isImm: false, rs: ^Word(0), rt: ^Word(0), expectRes: Word(0xFF_FF_FF_FE)}, // add t0, s1, s2 + {name: "add", funct: 0x20, isImm: false, rs: Word(0x7F_FF_FF_FF), rt: Word(0x7F_FF_FF_FF), expectRes: Word(0xFF_FF_FF_FE)}, // add t0, s1, s2 + {name: "add", funct: 0x20, isImm: false, rs: ^Word(0), rt: Word(2), expectRes: Word(1)}, // add t0, s1, s2 + {name: "add", funct: 0x20, isImm: false, rs: Word(2), rt: ^Word(0), expectRes: Word(1)}, // add t0, s1, s2 + {name: "add", funct: 0x20, isImm: false, rs: Word(0x7F_FF_FF_FF), rt: Word(1), expectRes: Word(0x80_00_00_00)}, // add t0, s1, s2 + + {name: "addu", funct: 0x21, isImm: false, rs: Word(12), rt: Word(20), expectRes: Word(32)}, // addu t0, s1, s2 + {name: "addu", funct: 0x21, isImm: false, rs: ^Word(0), rt: ^Word(0), expectRes: Word(0xFF_FF_FF_FE)}, // addu t0, s1, s2 + {name: "addu", funct: 0x21, isImm: false, rs: Word(0x7F_FF_FF_FF), rt: Word(0x7F_FF_FF_FF), expectRes: Word(0xFF_FF_FF_FE)}, // addu t0, s1, s2 + {name: "addu", funct: 0x21, isImm: false, rs: ^Word(0), rt: Word(2), expectRes: Word(1)}, // addu t0, s1, s2 + {name: "addu", funct: 0x21, isImm: false, rs: Word(0x7F_FF_FF_FF), rt: Word(1), expectRes: Word(0x80_00_00_00)}, // addu t0, s1, s2 + + {name: "addi", opcode: 0x8, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // addi t0, s1, 40 + {name: "addi", opcode: 0x8, isImm: true, rs: ^Word(0), rt: Word(0xAA_BB_CC_DD), imm: uint16(1), expectRes: Word(0)}, // addi t0, s1, 40 + {name: "addi", opcode: 0x8, isImm: true, rs: ^Word(0), rt: Word(0xAA_BB_CC_DD), imm: uint16(0xFF_FF), expectRes: Word(0xFF_FF_FF_FE)}, // addi t0, s1, 40 + {name: "addi sign", opcode: 0x8, isImm: true, rs: Word(2), rt: Word(1), imm: uint16(0xfffe), expectRes: Word(0)}, // addi t0, s1, -2 + + {name: "addiu", opcode: 0x9, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // addiu t0, s1, 40 + {name: "addiu", opcode: 0x9, isImm: true, rs: ^Word(0), rt: Word(0xAA_BB_CC_DD), imm: uint16(1), expectRes: Word(0)}, // addiu t0, s1, 40 + {name: "addiu", opcode: 0x9, isImm: true, rs: ^Word(0), rt: Word(0xAA_BB_CC_DD), imm: uint16(0xFF_FF), expectRes: Word(0xFF_FF_FF_FE)}, // addiu t0, s1, 40 + + {name: "sub", funct: 0x22, isImm: false, rs: Word(20), rt: Word(12), expectRes: Word(8)}, // sub t0, s1, s2 + {name: "sub", funct: 0x22, isImm: false, rs: ^Word(0), rt: Word(1), expectRes: Word(0xFF_FF_FF_FE)}, // sub t0, s1, s2 + {name: "sub", funct: 0x22, isImm: false, rs: Word(1), rt: ^Word(0), expectRes: Word(0x2)}, // sub t0, s1, s2 + + {name: "subu", funct: 0x23, isImm: false, rs: Word(20), rt: Word(12), expectRes: Word(8)}, // subu t0, s1, s2 + {name: "subu", funct: 0x23, isImm: false, rs: ^Word(0), rt: Word(1), expectRes: Word(0xFF_FF_FF_FE)}, // subu t0, s1, s2 + {name: "subu", funct: 0x23, isImm: false, rs: Word(1), rt: ^Word(0), expectRes: Word(0x2)}, // subu t0, s1, s2 + } + testOperators(t, cases, true) +} +func TestEVM_SingleStep_Bitwise32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_SingleStep_Bitwise64") + // bitwise operations that use the full word size + cases := []operatorTestCase{ + {name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2 + {name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40 + {name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2 + {name: "ori", opcode: 0xd, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // ori t0, s1, 40 + {name: "xor", funct: 0x26, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1370)}, // xor t0, s1, s2 + {name: "xori", opcode: 0xe, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // xori t0, s1, 40 + {name: "nor", funct: 0x27, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(4294965765)}, // nor t0, s1, s2 + {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 + {name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 + } + testOperators(t, cases, false) +} + +func TestEVM_SingleStep_LoadStore32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_SingleStep_LoadStore64") + loadMemVal := Word(0x11_22_33_44) + loadMemValNeg := Word(0xF1_F2_F3_F4) + rtVal := Word(0xaa_bb_cc_dd) + cases := []loadStoreTestCase{ + {name: "lb, offset=0", opcode: uint32(0x20), base: 0x100, imm: 0x20, memVal: loadMemVal, expectRes: 0x11}, + {name: "lb, offset=1", opcode: uint32(0x20), base: 0x100, imm: 0x1, memVal: loadMemVal, expectRes: 0x22}, + {name: "lb, offset=2", opcode: uint32(0x20), base: 0x100, imm: 0x2, memVal: loadMemVal, expectRes: 0x33}, + {name: "lb, offset=2, variation", opcode: uint32(0x20), base: 0x102, imm: 0x20, memVal: loadMemVal, expectRes: 0x33}, + {name: "lb, offset=4", opcode: uint32(0x20), base: 0x103, imm: 0x0, memVal: loadMemVal, expectRes: 0x44}, + {name: "lb, negative, offset=0", opcode: uint32(0x20), base: 0x100, imm: 0x0, memVal: loadMemValNeg, expectRes: 0xFF_FF_FF_F1}, + {name: "lb, negative, offset=1", opcode: uint32(0x20), base: 0x101, imm: 0x0, memVal: loadMemValNeg, expectRes: 0xFF_FF_FF_F2}, + {name: "lb, negative, offset=2", opcode: uint32(0x20), base: 0x102, imm: 0x0, memVal: loadMemValNeg, expectRes: 0xFF_FF_FF_F3}, + {name: "lb, negative, offset=3", opcode: uint32(0x20), base: 0x103, imm: 0x0, memVal: loadMemValNeg, expectRes: 0xFF_FF_FF_F4}, + {name: "lh, offset=0", opcode: uint32(0x21), base: 0x100, imm: 0x20, memVal: loadMemVal, expectRes: 0x11_22}, + {name: "lh, offset=1", opcode: uint32(0x21), base: 0x101, imm: 0x20, memVal: loadMemVal, expectRes: 0x11_22}, + {name: "lh, offset=2", opcode: uint32(0x21), base: 0x102, imm: 0x20, memVal: loadMemVal, expectRes: 0x33_44}, + {name: "lh, offset=3", opcode: uint32(0x21), base: 0x102, imm: 0x1, memVal: loadMemVal, expectRes: 0x33_44}, + {name: "lh, negative, offset=0", opcode: uint32(0x21), base: 0x100, imm: 0x20, memVal: loadMemValNeg, expectRes: 0xFF_FF_F1_F2}, + {name: "lh, negative, offset=3", opcode: uint32(0x21), base: 0x102, imm: 0x1, memVal: loadMemValNeg, expectRes: 0xFF_FF_F3_F4}, + {name: "lw", opcode: uint32(0x23), base: 0x100, imm: 0x20, memVal: loadMemVal, expectRes: 0x11_22_33_44}, + {name: "lbu", opcode: uint32(0x24), base: 0x100, imm: 0x20, memVal: loadMemVal, expectRes: 0x11}, + {name: "lbu, negative", opcode: uint32(0x24), base: 0x100, imm: 0x20, memVal: loadMemValNeg, expectRes: 0xF1}, + {name: "lhu", opcode: uint32(0x25), base: 0x100, imm: 0x20, memVal: loadMemVal, expectRes: 0x11_22}, + {name: "lhu, negative", opcode: uint32(0x25), base: 0x100, imm: 0x20, memVal: loadMemValNeg, expectRes: 0xF1_F2}, + {name: "lwl", opcode: uint32(0x22), base: 0x100, imm: 0x20, rt: rtVal, memVal: loadMemVal, expectRes: loadMemVal}, + {name: "lwl unaligned", opcode: uint32(0x22), base: 0x100, imm: 0x1, rt: rtVal, memVal: loadMemVal, expectRes: 0x22_33_44_dd}, + {name: "lwr", opcode: uint32(0x26), base: 0x100, imm: 0x20, rt: rtVal, memVal: loadMemVal, expectRes: 0xaa_bb_cc_11}, + {name: "lwr unaligned", opcode: uint32(0x26), base: 0x100, imm: 0x1, rt: rtVal, memVal: loadMemVal, expectRes: 0xaa_bb_11_22}, + {name: "sb, offset=0", opcode: uint32(0x28), base: 0x100, imm: 0x20, rt: rtVal, expectMemVal: 0xdd_00_00_00}, + {name: "sb, offset=1", opcode: uint32(0x28), base: 0x100, imm: 0x21, rt: rtVal, expectMemVal: 0x00_dd_00_00}, + {name: "sb, offset=2", opcode: uint32(0x28), base: 0x102, imm: 0x20, rt: rtVal, expectMemVal: 0x00_00_dd_00}, + {name: "sb, offset=3", opcode: uint32(0x28), base: 0x103, imm: 0x20, rt: rtVal, expectMemVal: 0x00_00_00_dd}, + {name: "sh, offset=0", opcode: uint32(0x29), base: 0x100, imm: 0x20, rt: rtVal, expectMemVal: 0xcc_dd_00_00}, + {name: "sh, offset=1", opcode: uint32(0x29), base: 0x100, imm: 0x21, rt: rtVal, expectMemVal: 0xcc_dd_00_00}, + {name: "sh, offset=2", opcode: uint32(0x29), base: 0x102, imm: 0x20, rt: rtVal, expectMemVal: 0x00_00_cc_dd}, + {name: "sh, offset=3", opcode: uint32(0x29), base: 0x102, imm: 0x21, rt: rtVal, expectMemVal: 0x00_00_cc_dd}, + {name: "swl", opcode: uint32(0x2a), base: 0x100, imm: 0x20, rt: rtVal, expectMemVal: 0xaa_bb_cc_dd}, + {name: "sw", opcode: uint32(0x2b), base: 0x100, imm: 0x20, rt: rtVal, expectMemVal: 0xaa_bb_cc_dd}, + {name: "swr unaligned", opcode: uint32(0x2e), base: 0x100, imm: 0x1, rt: rtVal, expectMemVal: 0xcc_dd_00_00}, + } + testLoadStore(t, cases) +} + +func TestEVM_SingleStep_Lui(t *testing.T) { versions := GetMipsVersionTestCases(t) + cases := []struct { - name string - isImm bool - rs Word - rt Word - imm uint16 - funct uint32 - opcode uint32 - expectRes Word + name string + rtReg uint32 + imm uint32 + expectRt Word }{ - {name: "add", funct: 0x20, isImm: false, rs: Word(12), rt: Word(20), expectRes: Word(32)}, // add t0, s1, s2 - {name: "addu", funct: 0x21, isImm: false, rs: Word(12), rt: Word(20), expectRes: Word(32)}, // addu t0, s1, s2 - {name: "addi", opcode: 0x8, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // addi t0, s1, 40 - {name: "addi sign", opcode: 0x8, isImm: true, rs: Word(2), rt: Word(1), imm: uint16(0xfffe), expectRes: Word(0)}, // addi t0, s1, -2 - {name: "addiu", opcode: 0x9, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // addiu t0, s1, 40 - {name: "sub", funct: 0x22, isImm: false, rs: Word(20), rt: Word(12), expectRes: Word(8)}, // sub t0, s1, s2 - {name: "subu", funct: 0x23, isImm: false, rs: Word(20), rt: Word(12), expectRes: Word(8)}, // subu t0, s1, s2 - {name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2 - {name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40 - {name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2 - {name: "ori", opcode: 0xd, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // ori t0, s1, 40 - {name: "xor", funct: 0x26, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1370)}, // xor t0, s1, s2 - {name: "xori", opcode: 0xe, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // xori t0, s1, 40 - {name: "nor", funct: 0x27, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(4294965765)}, // nor t0, s1, s2 - {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 - {name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 + {name: "lui unsigned", rtReg: 5, imm: 0x1234, expectRt: 0x1234_0000}, + {name: "lui signed", rtReg: 7, imm: 0x8765, expectRt: signExtend64(0x8765_0000)}, } for _, v := range versions { for i, tt := range cases { testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) state := goVm.GetState() - var insn uint32 - var baseReg uint32 = 17 - var rtReg uint32 - var rdReg uint32 - if tt.isImm { - rtReg = 8 - insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | uint32(tt.imm) - state.GetRegistersRef()[rtReg] = tt.rt - state.GetRegistersRef()[baseReg] = tt.rs - } else { - rtReg = 18 - rdReg = 8 - insn = baseReg<<21 | rtReg<<16 | rdReg<<11 | tt.funct - state.GetRegistersRef()[baseReg] = tt.rs - state.GetRegistersRef()[rtReg] = tt.rt - } - state.GetMemory().SetUint32(0, insn) + insn := 0b1111<<26 | uint32(tt.rtReg)<<16 | (tt.imm & 0xFFFF) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) step := state.GetStep() // Setup expectations expected := testutil.NewExpectedState(state) - expected.Step += 1 - expected.PC = 4 - expected.NextPC = 8 - if tt.isImm { - expected.Registers[rtReg] = tt.expectRes - } else { - expected.Registers[rdReg] = tt.expectRes - } - + expected.ExpectStep() + expected.Registers[tt.rtReg] = tt.expectRt stepWitness, err := goVm.Step(true) require.NoError(t, err) // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } } -func TestEVMSingleStep_LoadStore(t *testing.T) { - var tracer *tracing.Hooks - +func TestEVM_SingleStep_CloClz(t *testing.T) { versions := GetMipsVersionTestCases(t) + + rsReg := uint32(5) + rdReg := uint32(6) cases := []struct { - name string - rs Word - rt Word - isUnAligned bool - opcode uint32 - memVal Word - expectMemVal Word - expectRes Word + name string + rs Word + expectedResult Word + funct uint32 }{ - {name: "lb", opcode: uint32(0x20), memVal: Word(0x12_00_00_00), expectRes: Word(0x12)}, // lb $t0, 4($t1) - {name: "lh", opcode: uint32(0x21), memVal: Word(0x12_23_00_00), expectRes: Word(0x12_23)}, // lh $t0, 4($t1) - {name: "lw", opcode: uint32(0x23), memVal: Word(0x12_23_45_67), expectRes: Word(0x12_23_45_67)}, // lw $t0, 4($t1) - {name: "lbu", opcode: uint32(0x24), memVal: Word(0x12_23_00_00), expectRes: Word(0x12)}, // lbu $t0, 4($t1) - {name: "lhu", opcode: uint32(0x25), memVal: Word(0x12_23_00_00), expectRes: Word(0x12_23)}, // lhu $t0, 4($t1) - {name: "lwl", opcode: uint32(0x22), rt: Word(0xaa_bb_cc_dd), memVal: Word(0x12_34_56_78), expectRes: Word(0x12_34_56_78)}, // lwl $t0, 4($t1) - {name: "lwl unaligned address", opcode: uint32(0x22), rt: Word(0xaa_bb_cc_dd), isUnAligned: true, memVal: Word(0x12_34_56_78), expectRes: Word(0x34_56_78_dd)}, // lwl $t0, 5($t1) - {name: "lwr", opcode: uint32(0x26), rt: Word(0xaa_bb_cc_dd), memVal: Word(0x12_34_56_78), expectRes: Word(0xaa_bb_cc_12)}, // lwr $t0, 4($t1) - {name: "lwr unaligned address", opcode: uint32(0x26), rt: Word(0xaa_bb_cc_dd), isUnAligned: true, memVal: Word(0x12_34_56_78), expectRes: Word(0xaa_bb_12_34)}, // lwr $t0, 5($t1) - {name: "sb", opcode: uint32(0x28), rt: Word(0xaa_bb_cc_dd), expectMemVal: Word(0xdd_00_00_00)}, // sb $t0, 4($t1) - {name: "sh", opcode: uint32(0x29), rt: Word(0xaa_bb_cc_dd), expectMemVal: Word(0xcc_dd_00_00)}, // sh $t0, 4($t1) - {name: "swl", opcode: uint32(0x2a), rt: Word(0xaa_bb_cc_dd), expectMemVal: Word(0xaa_bb_cc_dd)}, // swl $t0, 4($t1) - {name: "sw", opcode: uint32(0x2b), rt: Word(0xaa_bb_cc_dd), expectMemVal: Word(0xaa_bb_cc_dd)}, // sw $t0, 4($t1) - {name: "swr unaligned address", opcode: uint32(0x2e), rt: Word(0xaa_bb_cc_dd), isUnAligned: true, expectMemVal: Word(0xcc_dd_00_00)}, // swr $t0, 5($t1) - } - - var t1 Word = 0x100 - var baseReg uint32 = 9 - var rtReg uint32 = 8 + {name: "clo", rs: 0xFFFF_FFFE, expectedResult: 31, funct: 0b10_0001}, + {name: "clo", rs: 0xE000_0000, expectedResult: 3, funct: 0b10_0001}, + {name: "clo", rs: 0x8000_0000, expectedResult: 1, funct: 0b10_0001}, + {name: "clo, sign-extended", rs: signExtend64(0x8000_0000), expectedResult: 1, funct: 0b10_0001}, + {name: "clo, sign-extended", rs: signExtend64(0xF800_0000), expectedResult: 5, funct: 0b10_0001}, + {name: "clz", rs: 0x1, expectedResult: 31, funct: 0b10_0000}, + {name: "clz", rs: 0x1000_0000, expectedResult: 3, funct: 0b10_0000}, + {name: "clz", rs: 0x8000_0000, expectedResult: 0, funct: 0b10_0000}, + {name: "clz, sign-extended", rs: signExtend64(0x8000_0000), expectedResult: 0, funct: 0b10_0000}, + } + for _, v := range versions { for i, tt := range cases { testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + // Set up state + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) state := goVm.GetState() - var insn uint32 - imm := uint32(0x4) - if tt.isUnAligned { - imm = uint32(0x5) - } - - insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | imm - state.GetRegistersRef()[rtReg] = tt.rt - state.GetRegistersRef()[baseReg] = t1 - - state.GetMemory().SetUint32(0, insn) - state.GetMemory().SetWord(t1+4, tt.memVal) + insn := 0b01_1100<<26 | rsReg<<21 | rdReg<<11 | tt.funct + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[rsReg] = tt.rs step := state.GetStep() // Setup expectations expected := testutil.NewExpectedState(state) expected.ExpectStep() - - if tt.expectMemVal != 0 { - expected.ExpectMemoryWriteWord(t1+4, tt.expectMemVal) - } else { - expected.Registers[rtReg] = tt.expectRes - } + expected.Registers[rdReg] = tt.expectedResult stepWitness, err := goVm.Step(true) require.NoError(t, err) // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } } -func TestEVMSingleStep_MovzMovn(t *testing.T) { - var tracer *tracing.Hooks +func TestEVM_SingleStep_MovzMovn(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -344,7 +363,7 @@ func TestEVMSingleStep_MovzMovn(t *testing.T) { state.GetRegistersRef()[rtReg] = t2 state.GetRegistersRef()[rsReg] = Word(0xb) state.GetRegistersRef()[rdReg] = Word(0xa) - state.GetMemory().SetUint32(0, insn) + testutil.StoreInstruction(state.GetMemory(), 0, insn) step := state.GetStep() // Setup expectations expected := testutil.NewExpectedState(state) @@ -355,7 +374,7 @@ func TestEVMSingleStep_MovzMovn(t *testing.T) { require.NoError(t, err) // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) if tt.funct == 0xa { t2 = 0x1 @@ -371,16 +390,250 @@ func TestEVMSingleStep_MovzMovn(t *testing.T) { require.NoError(t, err) // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } } -func TestEVM_MMap(t *testing.T) { - var tracer *tracing.Hooks +func TestEVM_SingleStep_MfhiMflo(t *testing.T) { + versions := GetMipsVersionTestCases(t) + cases := []struct { + name string + funct uint32 + hi Word + lo Word + }{ + {name: "mflo", funct: uint32(0x12), lo: Word(0xdeadbeef), hi: Word(0x0)}, + {name: "mfhi", funct: uint32(0x10), lo: Word(0x0), hi: Word(0xdeadbeef)}, + } + expect := Word(0xdeadbeef) + for _, v := range versions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithLO(tt.lo), testutil.WithHI(tt.hi)) + state := goVm.GetState() + rdReg := uint32(8) + insn := rdReg<<11 | tt.funct + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + step := state.GetStep() + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + expected.Registers[rdReg] = expect + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } + } +} + +func TestEVM_SingleStep_MulDiv(t *testing.T) { + cases := []mulDivTestCase{ + {name: "mul", funct: uint32(0x2), opcode: uint32(28), rs: Word(5), rt: Word(2), rdReg: uint32(0x8), expectRes: Word(10)}, // mul t0, t1, t2 + {name: "mul", funct: uint32(0x2), opcode: uint32(28), rs: Word(0x1), rt: ^Word(0), rdReg: uint32(0x8), expectRes: ^Word(0)}, // mul t1, t2 + {name: "mul", funct: uint32(0x2), opcode: uint32(28), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x8), expectRes: Word(0x1)}, // mul t1, t2 + {name: "mul", funct: uint32(0x2), opcode: uint32(28), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD), rdReg: uint32(0x8), expectRes: Word(0xFC_FC_FD_27)}, // mul t1, t2 + + {name: "mult", funct: uint32(0x18), rs: Word(0x0F_FF_00_00), rt: Word(100), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0x6), expectLo: Word(0x3F_9C_00_00)}, // mult t1, t2 + {name: "mult", funct: uint32(0x18), rs: Word(0x1), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xFF_FF_FF_FF), expectLo: Word(0xFF_FF_FF_FF)}, // mult t1, t2 + {name: "mult", funct: uint32(0x18), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0), expectLo: Word(0x1)}, // mult t1, t2 + {name: "mult", funct: uint32(0x18), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xE), expectLo: Word(0xFC_FC_FD_27)}, // mult t1, t2 + + {name: "multu", funct: uint32(0x19), rs: Word(0x0F_FF_00_00), rt: Word(100), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0x6), expectLo: Word(0x3F_9C_00_00)}, // multu t1, t2 + {name: "multu", funct: uint32(0x19), rs: Word(0x1), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0x0), expectLo: Word(0xFF_FF_FF_FF)}, // multu t1, t2 + {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xFF_FF_FF_FE), expectLo: Word(0x1)}, // multu t1, t2 + {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xAA_BB_CC_BE), expectLo: Word(0xFC_FC_FD_27)}, // multu t1, t2 + {name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_BE), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(0xAA_BB_CC_9F), expectLo: Word(0xFC_FD_02_9A)}, // multu t1, t2 + + {name: "div", funct: uint32(0x1a), rs: Word(5), rt: Word(2), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(1), expectLo: Word(2)}, // div t1, t2 + {name: "div by zero", funct: uint32(0x1a), rs: Word(5), rt: Word(0), rdReg: uint32(0x0), opcode: uint32(0), panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, // div t1, t2 + {name: "divu", funct: uint32(0x1b), rs: Word(5), rt: Word(2), rdReg: uint32(0x0), opcode: uint32(0), expectHi: Word(1), expectLo: Word(2)}, // divu t1, t2 + {name: "divu by zero", funct: uint32(0x1b), rs: Word(5), rt: Word(0), rdReg: uint32(0x0), opcode: uint32(0), panicMsg: "instruction divide by zero", revertMsg: "division by zero"}, // divu t1, t2 + } + + testMulDiv(t, cases, true) +} + +func TestEVM_SingleStep_MthiMtlo(t *testing.T) { + versions := GetMipsVersionTestCases(t) + cases := []struct { + name string + funct uint32 + }{ + {name: "mtlo", funct: uint32(0x13)}, + {name: "mthi", funct: uint32(0x11)}, + } + val := Word(0xdeadbeef) + for _, v := range versions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) + state := goVm.GetState() + rsReg := uint32(8) + insn := rsReg<<21 | tt.funct + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[rsReg] = val + step := state.GetStep() + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + if tt.funct == 0x11 { + expected.HI = state.GetRegistersRef()[rsReg] + } else { + expected.LO = state.GetRegistersRef()[rsReg] + } + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } + } +} + +func TestEVM_SingleStep_BeqBne(t *testing.T) { + versions := GetMipsVersionTestCases(t) + cases := []struct { + name string + imm uint32 + opcode uint32 + rs Word + rt Word + }{ + {name: "bne", opcode: uint32(0x5), imm: uint32(0x10), rs: Word(0xaa), rt: Word(0xdeadbeef)}, // bne $t0, $t1, 16 + {name: "beq", opcode: uint32(0x4), imm: uint32(0x10), rs: Word(0xdeadbeef), rt: Word(0xdeadbeef)}, // beq $t0, $t1, 16 + } + for _, v := range versions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + state := goVm.GetState() + rsReg := uint32(9) + rtReg := uint32(8) + insn := tt.opcode<<26 | rsReg<<21 | rtReg<<16 | tt.imm + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[rsReg] = tt.rs + testutil.StoreInstruction(state.GetMemory(), 0, insn) + step := state.GetStep() + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.Step = state.GetStep() + 1 + expected.PC = state.GetCpu().NextPC + expected.NextPC = state.GetCpu().NextPC + Word(tt.imm<<2) + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } + } + +} + +func TestEVM_SingleStep_SlSr(t *testing.T) { + versions := GetMipsVersionTestCases(t) + cases := []struct { + name string + rs Word + rt Word + rsReg uint32 + funct uint16 + expectVal Word + }{ + {name: "sll", funct: uint16(4) << 6, rt: Word(0x20), rsReg: uint32(0x0), expectVal: Word(0x20) << uint8(4)}, // sll t0, t1, 3 + {name: "srl", funct: uint16(4)<<6 | 2, rt: Word(0x20), rsReg: uint32(0x0), expectVal: Word(0x20) >> uint8(4)}, // srl t0, t1, 3 + {name: "sra", funct: uint16(4)<<6 | 3, rt: Word(0x80_00_00_20), rsReg: uint32(0x0), expectVal: signExtend64(0xF8_00_00_02)}, // sra t0, t1, 3 + {name: "sllv", funct: uint16(4), rt: Word(0x20), rs: Word(4), rsReg: uint32(0xa), expectVal: Word(0x20) << Word(4)}, // sllv t0, t1, t2 + {name: "srlv", funct: uint16(6), rt: Word(0x20_00), rs: Word(4), rsReg: uint32(0xa), expectVal: Word(0x20_00) >> Word(4)}, // srlv t0, t1, t2 + {name: "srav", funct: uint16(7), rt: Word(0xdeafbeef), rs: Word(12), rsReg: uint32(0xa), expectVal: signExtend64(Word(0xfffdeafb))}, // srav t0, t1, t2 + } + + for _, v := range versions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + state := goVm.GetState() + var insn uint32 + rtReg := uint32(0x9) + rdReg := uint32(0x8) + insn = tt.rsReg<<21 | rtReg<<16 | rdReg<<11 | uint32(tt.funct) + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[tt.rsReg] = tt.rs + testutil.StoreInstruction(state.GetMemory(), 0, insn) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + + expected.Registers[rdReg] = tt.expectVal + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } + } +} +func TestEVM_SingleStep_JrJalr(t *testing.T) { + versions := GetMipsVersionTestCases(t) + cases := []struct { + name string + funct uint16 + rdReg uint32 + expectLink bool + }{ + {name: "jr", funct: uint16(0x8), rdReg: uint32(0)}, // jr t0 + {name: "jalr", funct: uint16(0x9), rdReg: uint32(0x9), expectLink: true}, // jalr t1, t0 + } + for _, v := range versions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + state := goVm.GetState() + rsReg := uint32(8) + insn := rsReg<<21 | tt.rdReg<<11 | uint32(tt.funct) + state.GetRegistersRef()[rsReg] = Word(0x34) + testutil.StoreInstruction(state.GetMemory(), 0, insn) + step := state.GetStep() + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.Step = state.GetStep() + 1 + expected.PC = state.GetCpu().NextPC + expected.NextPC = state.GetRegistersRef()[rsReg] + if tt.expectLink { + expected.Registers[tt.rdReg] = state.GetPC() + 8 + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } + } +} + +func TestEVM_MMap(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -408,7 +661,7 @@ func TestEVM_MMap(t *testing.T) { goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithHeap(c.heap)) state := goVm.GetState() - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysMmap state.GetRegistersRef()[4] = c.address state.GetRegistersRef()[5] = c.size @@ -437,15 +690,13 @@ func TestEVM_MMap(t *testing.T) { // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } } -func TestEVMSysWriteHint(t *testing.T) { - var tracer *tracing.Hooks - +func TestEVM_SysWriteHint(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { name string @@ -616,7 +867,7 @@ func TestEVMSysWriteHint(t *testing.T) { err := state.GetMemory().SetMemoryRange(arch.Word(tt.memOffset), bytes.NewReader(tt.hintData)) require.NoError(t, err) - state.GetMemory().SetUint32(state.GetPC(), insn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) step := state.GetStep() expected := testutil.NewExpectedState(state) @@ -632,35 +883,50 @@ func TestEVMSysWriteHint(t *testing.T) { expected.Validate(t, state) require.Equal(t, tt.expectedHints, oracle.Hints()) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } } -func TestEVMFault(t *testing.T) { +func TestEVM_Fault(t *testing.T) { var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer versions := GetMipsVersionTestCases(t) + + misAlignedInstructionErr := func() testutil.ErrMatcher { + if arch.IsMips32 { + // matches revert(0,0) + return testutil.CreateNoopErrorMatcher() + } else { + return testutil.CreateCustomErrorMatcher("InvalidPC()") + } + } + cases := []struct { name string + pc arch.Word nextPC arch.Word insn uint32 - errMsg string + errMsg testutil.ErrMatcher memoryProofAddresses []Word }{ - {"illegal instruction", 0, 0xFF_FF_FF_FF, "invalid instruction", []Word{0xa7ef00cc}}, - {"branch in delay-slot", 8, 0x11_02_00_03, "branch in delay slot", []Word{}}, - {"jump in delay-slot", 8, 0x0c_00_00_0c, "jump in delay slot", []Word{}}, + {name: "illegal instruction", nextPC: 0, insn: 0b111110 << 26, errMsg: testutil.CreateErrorStringMatcher("invalid instruction"), memoryProofAddresses: []Word{0x0}}, // memoryProof for the zero address at register 0 (+ imm) + {name: "branch in delay-slot", nextPC: 8, insn: 0x11_02_00_03, errMsg: testutil.CreateErrorStringMatcher("branch in delay slot")}, + {name: "jump in delay-slot", nextPC: 8, insn: 0x0c_00_00_0c, errMsg: testutil.CreateErrorStringMatcher("jump in delay slot")}, + {name: "misaligned instruction", pc: 1, nextPC: 4, insn: 0b110111_00001_00001 << 16, errMsg: misAlignedInstructionErr()}, + {name: "misaligned instruction", pc: 2, nextPC: 4, insn: 0b110111_00001_00001 << 16, errMsg: misAlignedInstructionErr()}, + {name: "misaligned instruction", pc: 3, nextPC: 4, insn: 0b110111_00001_00001 << 16, errMsg: misAlignedInstructionErr()}, + {name: "misaligned instruction", pc: 5, nextPC: 4, insn: 0b110111_00001_00001 << 16, errMsg: misAlignedInstructionErr()}, } for _, v := range versions { for _, tt := range cases { testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithNextPC(tt.nextPC)) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithPC(tt.pc), testutil.WithNextPC(tt.nextPC)) state := goVm.GetState() - state.GetMemory().SetUint32(0, tt.insn) + testutil.StoreInstruction(state.GetMemory(), 0, tt.insn) // set the return address ($ra) to jump into when test completes state.GetRegistersRef()[31] = testutil.EndAddr @@ -672,43 +938,39 @@ func TestEVMFault(t *testing.T) { } } -func TestHelloEVM(t *testing.T) { +func TestEVM_HelloProgram(t *testing.T) { + if os.Getenv("SKIP_SLOW_TESTS") == "true" { + t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") + } + t.Parallel() - var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer versions := GetMipsVersionTestCases(t) for _, v := range versions { v := v t.Run(v.Name, func(t *testing.T) { t.Parallel() - evm := testutil.NewMIPSEVM(v.Contracts) - evm.SetTracer(tracer) - testutil.LogStepFailureAtCleanup(t, evm) + validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts) var stdOutBuf, stdErrBuf bytes.Buffer - elfFile := "../../testdata/example/bin/hello.elf" + elfFile := testutil.ProgramPath("hello") goVm := v.ElfVMFactory(t, elfFile, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger()) state := goVm.GetState() start := time.Now() - for i := 0; i < 400_000; i++ { + for i := 0; i < 430_000; i++ { step := goVm.GetState().GetStep() if goVm.GetState().GetExited() { break } - insn := state.GetMemory().GetUint32(state.GetPC()) - if i%1000 == 0 { // avoid spamming test logs, we are executing many steps + insn := testutil.GetInstruction(state.GetMemory(), state.GetPC()) + if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn) } stepWitness, err := goVm.Step(true) require.NoError(t, err) - evmPost := evm.Step(t, stepWitness, step, v.StateHashFn) - // verify the post-state matches. - // TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison. - goPost, _ := goVm.GetState().EncodeWitness() - require.Equalf(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), - "mipsevm produced different state than EVM. insn: %x", insn) + validator.ValidateEVM(t, stepWitness, step, goVm) } end := time.Now() delta := end.Sub(start) @@ -723,23 +985,23 @@ func TestHelloEVM(t *testing.T) { } } -func TestClaimEVM(t *testing.T) { +func TestEVM_ClaimProgram(t *testing.T) { + if os.Getenv("SKIP_SLOW_TESTS") == "true" { + t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") + } + t.Parallel() - var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer versions := GetMipsVersionTestCases(t) for _, v := range versions { v := v t.Run(v.Name, func(t *testing.T) { t.Parallel() - evm := testutil.NewMIPSEVM(v.Contracts) - evm.SetTracer(tracer) - testutil.LogStepFailureAtCleanup(t, evm) - + validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts) oracle, expectedStdOut, expectedStdErr := testutil.ClaimTestOracle(t) var stdOutBuf, stdErrBuf bytes.Buffer - elfFile := "../../testdata/example/bin/claim.elf" + elfFile := testutil.ProgramPath("claim") goVm := v.ElfVMFactory(t, elfFile, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger()) state := goVm.GetState() @@ -749,19 +1011,14 @@ func TestClaimEVM(t *testing.T) { break } - insn := state.GetMemory().GetUint32(state.GetPC()) - if i%1000 == 0 { // avoid spamming test logs, we are executing many steps + insn := testutil.GetInstruction(state.GetMemory(), state.GetPC()) + if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn) } stepWitness, err := goVm.Step(true) require.NoError(t, err) - - evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn) - - goPost, _ := goVm.GetState().EncodeWitness() - require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), - "mipsevm produced different state than EVM") + validator.ValidateEVM(t, stepWitness, curStep, goVm) } require.True(t, state.GetExited(), "must complete program") @@ -773,42 +1030,39 @@ func TestClaimEVM(t *testing.T) { } } -func TestEntryEVM(t *testing.T) { +func TestEVM_EntryProgram(t *testing.T) { + if os.Getenv("SKIP_SLOW_TESTS") == "true" { + t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") + } + t.Parallel() - var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer versions := GetMipsVersionTestCases(t) for _, v := range versions { v := v t.Run(v.Name, func(t *testing.T) { t.Parallel() - evm := testutil.NewMIPSEVM(v.Contracts) - evm.SetTracer(tracer) - testutil.LogStepFailureAtCleanup(t, evm) + validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts) var stdOutBuf, stdErrBuf bytes.Buffer - elfFile := "../../testdata/example/bin/entry.elf" + elfFile := testutil.ProgramPath("entry") goVm := v.ElfVMFactory(t, elfFile, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger()) state := goVm.GetState() start := time.Now() - for i := 0; i < 400_000; i++ { + for i := 0; i < 500_000; i++ { curStep := goVm.GetState().GetStep() if goVm.GetState().GetExited() { break } - insn := state.GetMemory().GetUint32(state.GetPC()) + insn := testutil.GetInstruction(state.GetMemory(), state.GetPC()) if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn) } stepWitness, err := goVm.Step(true) require.NoError(t, err) - evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn) - // verify the post-state matches. - goPost, _ := goVm.GetState().EncodeWitness() - require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), - "mipsevm produced different state than EVM") + validator.ValidateEVM(t, stepWitness, curStep, goVm) } end := time.Now() delta := end.Sub(start) @@ -819,3 +1073,63 @@ func TestEntryEVM(t *testing.T) { }) } } + +func TestEVM_SingleStep_Branch32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_SingleStep_Branch64") + t.Parallel() + cases := []branchTestCase{ + // blez + {name: "blez", pc: 0, opcode: 0x6, rs: 0x5, offset: 0x100, expectNextPC: 0x8}, + {name: "blez large rs", pc: 0x10, opcode: 0x6, rs: 0x7F_FF_FF_FF, offset: 0x100, expectNextPC: 0x18}, + {name: "blez zero rs", pc: 0x10, opcode: 0x6, rs: 0x0, offset: 0x100, expectNextPC: 0x414}, + {name: "blez sign rs", pc: 0x10, opcode: 0x6, rs: -1, offset: 0x100, expectNextPC: 0x414}, + {name: "blez rs only sign bit set", pc: 0x10, opcode: 0x6, rs: testutil.ToSignedInteger(0x80_00_00_00), offset: 0x100, expectNextPC: 0x414}, + {name: "blez sign-extended offset", pc: 0x10, opcode: 0x6, rs: -1, offset: 0x80_00, expectNextPC: 0xFF_FE_00_14}, + + // bgtz + {name: "bgtz", pc: 0, opcode: 0x7, rs: 0x5, offset: 0x100, expectNextPC: 0x404}, + {name: "bgtz sign-extended offset", pc: 0x10, opcode: 0x7, rs: 0x5, offset: 0x80_00, expectNextPC: 0xFF_FE_00_14}, + {name: "bgtz large rs", pc: 0x10, opcode: 0x7, rs: 0x7F_FF_FF_FF, offset: 0x100, expectNextPC: 0x414}, + {name: "bgtz zero rs", pc: 0x10, opcode: 0x7, rs: 0x0, offset: 0x100, expectNextPC: 0x18}, + {name: "bgtz sign rs", pc: 0x10, opcode: 0x7, rs: -1, offset: 0x100, expectNextPC: 0x18}, + {name: "bgtz rs only sign bit set", pc: 0x10, opcode: 0x7, rs: testutil.ToSignedInteger(0x80_00_00_00), offset: 0x100, expectNextPC: 0x18}, + + // bltz t0, $x + {name: "bltz", pc: 0, opcode: 0x1, regimm: 0x0, rs: 0x5, offset: 0x100, expectNextPC: 0x8}, + {name: "bltz large rs", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: 0x7F_FF_FF_FF, offset: 0x100, expectNextPC: 0x18}, + {name: "bltz zero rs", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: 0x0, offset: 0x100, expectNextPC: 0x18}, + {name: "bltz sign rs", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: -1, offset: 0x100, expectNextPC: 0x414}, + {name: "bltz rs only sign bit set", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: testutil.ToSignedInteger(0x80_00_00_00), offset: 0x100, expectNextPC: 0x414}, + {name: "bltz sign-extended offset", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: -1, offset: 0x80_00, expectNextPC: 0xFF_FE_00_14}, + {name: "bltz large offset no-sign", pc: 0x10, opcode: 0x1, regimm: 0x0, rs: -1, offset: 0x7F_FF, expectNextPC: 0x2_00_10}, + + // bltzal t0, $x + {name: "bltzal", pc: 0, opcode: 0x1, regimm: 0x10, rs: 0x5, offset: 0x100, expectNextPC: 0x8, expectLink: true}, + {name: "bltzal large rs", pc: 0x10, opcode: 0x1, regimm: 0x10, rs: 0x7F_FF_FF_FF, offset: 0x100, expectNextPC: 0x18, expectLink: true}, + {name: "bltzal zero rs", pc: 0x10, opcode: 0x1, regimm: 0x10, rs: 0x0, offset: 0x100, expectNextPC: 0x18, expectLink: true}, + {name: "bltzal sign rs", pc: 0x10, opcode: 0x1, regimm: 0x10, rs: -1, offset: 0x100, expectNextPC: 0x414, expectLink: true}, + {name: "bltzal rs only sign bit set", pc: 0x10, opcode: 0x1, regimm: 0x10, rs: testutil.ToSignedInteger(0x80_00_00_00), offset: 0x100, expectNextPC: 0x414, expectLink: true}, + {name: "bltzal sign-extended offset", pc: 0x10, opcode: 0x1, regimm: 0x10, rs: -1, offset: 0x80_00, expectNextPC: 0xFF_FE_00_14, expectLink: true}, + {name: "bltzal large offset no-sign", pc: 0x10, opcode: 0x1, regimm: 0x10, rs: -1, offset: 0x7F_FF, expectNextPC: 0x2_00_10, expectLink: true}, + + // bgez t0, $x + {name: "bgez", pc: 0, opcode: 0x1, regimm: 0x1, rs: 0x5, offset: 0x100, expectNextPC: 0x404}, + {name: "bgez large rs", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 0x7F_FF_FF_FF, offset: 0x100, expectNextPC: 0x414}, + {name: "bgez zero rs", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 0x0, offset: 0x100, expectNextPC: 0x414}, + {name: "bgez branch not taken", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: -1, offset: 0x100, expectNextPC: 0x18}, + {name: "bgez sign-extended offset", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 1, offset: 0x80_00, expectNextPC: 0xFF_FE_00_14}, + {name: "bgez large offset no-sign", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 1, offset: 0x70_00, expectNextPC: 0x1_C0_14}, + {name: "bgez fill bit offset except sign", pc: 0x10, opcode: 0x1, regimm: 0x1, rs: 1, offset: 0x7F_FF, expectNextPC: 0x2_00_10}, + + // bgezal t0, $x + {name: "bgezal", pc: 0, opcode: 0x1, regimm: 0x11, rs: 0x5, offset: 0x100, expectNextPC: 0x404, expectLink: true}, + {name: "bgezal large rs", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 0x7F_FF_FF_FF, offset: 0x100, expectNextPC: 0x414, expectLink: true}, + {name: "bgezal zero rs", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 0x0, offset: 0x100, expectNextPC: 0x414, expectLink: true}, + {name: "bgezal branch not taken", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: -1, offset: 0x100, expectNextPC: 0x18, expectLink: true}, + {name: "bgezal sign-extended offset", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x80_00, expectNextPC: 0xFF_FE_00_14, expectLink: true}, + {name: "bgezal large offset no-sign", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x70_00, expectNextPC: 0x1_C0_14, expectLink: true}, + {name: "bgezal fill bit offset except sign", pc: 0x10, opcode: 0x1, regimm: 0x11, rs: 1, offset: 0x7F_FF, expectNextPC: 0x2_00_10, expectLink: true}, + } + + testBranch(t, cases) +} diff --git a/cannon/mipsevm/tests/evm_multithreaded64_test.go b/cannon/mipsevm/tests/evm_multithreaded64_test.go new file mode 100644 index 0000000000000..911a0d256a3c6 --- /dev/null +++ b/cannon/mipsevm/tests/evm_multithreaded64_test.go @@ -0,0 +1,484 @@ +//go:build cannon64 +// +build cannon64 + +// These tests target architectures that are 64-bit or larger +package tests + +import ( + "encoding/binary" + "fmt" + "slices" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" + mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" +) + +func TestEVM_MT64_LL(t *testing.T) { + memVal := Word(0x11223344_55667788) + memValNeg := Word(0xF1223344_F5667788) + cases := []struct { + name string + base Word + offset int + addr Word + memVal Word + retReg int + retVal Word + }{ + {name: "8-byte-aligned addr", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retVal: 0x11223344, retReg: 5}, + {name: "8-byte-aligned addr, neg value", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memValNeg, retVal: 0xFFFFFFFF_F1223344, retReg: 5}, + {name: "8-byte-aligned addr, extra bits", base: 0x01, offset: 0x0109, addr: 0x010A, memVal: memVal, retVal: 0x11223344, retReg: 5}, + {name: "8-byte-aligned addr, addr signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, memVal: memVal, retVal: 0x11223344, retReg: 5}, + {name: "8-byte-aligned addr, addr signed extended w overflow", base: 0x1000_0001, offset: 0xFF07, addr: 0x0000_0000_0FFF_FF08, memVal: memVal, retVal: 0x11223344, retReg: 5}, + {name: "4-byte-aligned addr", base: 0x01, offset: 0x0103, addr: 0x0104, memVal: memVal, retVal: 0x55667788, retReg: 5}, + {name: "4-byte-aligned addr, neg value", base: 0x01, offset: 0x0104, addr: 0x0105, memVal: memValNeg, retVal: 0xFFFFFFFF_F5667788, retReg: 5}, + {name: "4-byte-aligned addr, extra bits", base: 0x01, offset: 0x0105, addr: 0x0106, memVal: memVal, retVal: 0x55667788, retReg: 5}, + {name: "4-byte-aligned addr, addr signed extended", base: 0x01, offset: 0xFF33, addr: 0xFFFF_FFFF_FFFF_FF34, memVal: memVal, retVal: 0x55667788, retReg: 5}, + {name: "4-byte-aligned addr, addr signed extended w overflow", base: 0x1000_0001, offset: 0xFF03, addr: 0x0000_0000_0FFF_FF04, memVal: memVal, retVal: 0x55667788, retReg: 5}, + {name: "Return register set to 0", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retVal: 0x11223344, retReg: 0}, + } + for i, c := range cases { + for _, withExistingReservation := range []bool{true, false} { + tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + retReg := c.retReg + baseReg := 6 + insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40)) + step := state.GetStep() + + // Set up state + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetMemory().SetWord(effAddr, c.memVal) + state.GetRegistersRef()[baseReg] = c.base + if withExistingReservation { + state.LLReservationStatus = multithreaded.LLStatusActive32bit + state.LLAddress = c.addr + 1 + state.LLOwnerThread = 123 + } else { + state.LLReservationStatus = multithreaded.LLStatusNone + state.LLAddress = 0 + state.LLOwnerThread = 0 + } + + // Set up expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.LLReservationStatus = multithreaded.LLStatusActive32bit + expected.LLAddress = c.addr + expected.LLOwnerThread = state.GetCurrentThread().ThreadId + if retReg != 0 { + expected.ActiveThread().Registers[retReg] = c.retVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } + } +} + +func TestEVM_MT64_SC(t *testing.T) { + llVariations := []struct { + name string + llReservationStatus multithreaded.LLReservationStatus + matchThreadId bool + matchAddr bool + shouldSucceed bool + }{ + {name: "should succeed", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: true, shouldSucceed: true}, + {name: "mismatch addr", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: true, shouldSucceed: false}, + {name: "mismatched thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: false, shouldSucceed: false}, + {name: "mismatched addr & thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: false, shouldSucceed: false}, + {name: "mismatched reservation status", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: true, shouldSucceed: false}, + {name: "no active reservation", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchAddr: true, shouldSucceed: false}, + } + + cases := []struct { + name string + base Word + offset int + addr Word + value Word + expectedMemVal Word + rtReg int + threadId Word + }{ + {name: "8-byte-aligned addr", base: 0x01, offset: 0x0137, addr: 0x0138, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4}, + {name: "8-byte-aligned addr, extra bits", base: 0x01, offset: 0x0138, addr: 0x0139, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4}, + {name: "8-byte-aligned addr, signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4}, + {name: "8-byte-aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF37, addr: 0x0FFF_FF38, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4}, + {name: "4-byte-aligned addr", base: 0x01, offset: 0x0133, addr: 0x0134, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4}, + {name: "4-byte-aligned addr, extra bits", base: 0x01, offset: 0x0134, addr: 0x0135, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4}, + {name: "4-byte-aligned addr, signed extended", base: 0x01, offset: 0xFF33, addr: 0xFFFF_FFFF_FFFF_FF34, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4}, + {name: "4-byte-aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF33, addr: 0x0FFF_FF34, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4}, + {name: "Return register set to 0", base: 0x01, offset: 0x0138, addr: 0x0139, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 0, threadId: 4}, + {name: "Zero valued ll args", base: 0x0, offset: 0x0, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 0}, + } + for i, c := range cases { + for _, v := range llVariations { + tName := fmt.Sprintf("%v (%v)", c.name, v.name) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + // Setup + rtReg := c.rtReg + baseReg := 6 + insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm, state, contracts := setup(t, i, nil) + mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) + step := state.GetStep() + + // Define LL-related params + var llAddress, llOwnerThread Word + if v.matchAddr { + llAddress = c.addr + } else { + llAddress = c.addr + 1 + } + if v.matchThreadId { + llOwnerThread = c.threadId + } else { + llOwnerThread = c.threadId + 1 + } + + // Setup state + state.GetCurrentThread().ThreadId = c.threadId + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[baseReg] = c.base + state.GetRegistersRef()[rtReg] = c.value + state.LLReservationStatus = v.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + var retVal Word + if v.shouldSucceed { + retVal = 1 + expected.ExpectMemoryWordWrite(effAddr, c.expectedMemVal) + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } else { + retVal = 0 + } + if rtReg != 0 { + expected.ActiveThread().Registers[rtReg] = retVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } + } +} + +func TestEVM_MT64_LLD(t *testing.T) { + memVal := Word(0x11223344_55667788) + memValNeg := Word(0xF1223344_F5667788) + cases := []struct { + name string + base Word + offset int + addr Word + memVal Word + retReg int + }{ + {name: "Aligned addr", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retReg: 5}, + {name: "Aligned addr, neg value", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memValNeg, retReg: 5}, + {name: "Unaligned addr, offset=1", base: 0x01, offset: 0x0100, addr: 0x0101, memVal: memVal, retReg: 5}, + {name: "Unaligned addr, offset=2", base: 0x02, offset: 0x0100, addr: 0x0102, memVal: memVal, retReg: 5}, + {name: "Unaligned addr, offset=3", base: 0x03, offset: 0x0100, addr: 0x0103, memVal: memVal, retReg: 5}, + {name: "Unaligned addr, offset=4", base: 0x04, offset: 0x0100, addr: 0x0104, memVal: memVal, retReg: 5}, + {name: "Unaligned addr, offset=5", base: 0x05, offset: 0x0100, addr: 0x0105, memVal: memVal, retReg: 5}, + {name: "Unaligned addr, offset=6", base: 0x06, offset: 0x0100, addr: 0x0106, memVal: memVal, retReg: 5}, + {name: "Unaligned addr, offset=7", base: 0x07, offset: 0x0100, addr: 0x0107, memVal: memVal, retReg: 5}, + {name: "Aligned addr, signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, memVal: memVal, retReg: 5}, + {name: "Aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF07, addr: 0x0000_0000_0FFF_FF08, memVal: memVal, retReg: 5}, + {name: "Return register set to 0", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retReg: 0}, + } + for i, c := range cases { + for _, withExistingReservation := range []bool{true, false} { + tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + retReg := c.retReg + baseReg := 6 + insn := uint32((0b11_0100 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40)) + step := state.GetStep() + + // Set up state + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetMemory().SetWord(effAddr, c.memVal) + state.GetRegistersRef()[baseReg] = c.base + if withExistingReservation { + state.LLReservationStatus = multithreaded.LLStatusActive64bit + state.LLAddress = c.addr + 1 + state.LLOwnerThread = 123 + } else { + state.LLReservationStatus = multithreaded.LLStatusNone + state.LLAddress = 0 + state.LLOwnerThread = 0 + } + + // Set up expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.LLReservationStatus = multithreaded.LLStatusActive64bit + expected.LLAddress = c.addr + expected.LLOwnerThread = state.GetCurrentThread().ThreadId + if retReg != 0 { + expected.ActiveThread().Registers[retReg] = c.memVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } + } +} + +func TestEVM_MT64_SCD(t *testing.T) { + value := Word(0x11223344_55667788) + llVariations := []struct { + name string + llReservationStatus multithreaded.LLReservationStatus + matchThreadId bool + matchAddr bool + shouldSucceed bool + }{ + {name: "should succeed", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: true, shouldSucceed: true}, + {name: "mismatch addr", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, matchAddr: true, shouldSucceed: false}, + {name: "mismatched thread", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: false, shouldSucceed: false}, + {name: "mismatched addr & thread", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, matchAddr: false, shouldSucceed: false}, + {name: "mismatched status", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: true, shouldSucceed: false}, + {name: "no active reservation", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchAddr: true, shouldSucceed: false}, + } + + cases := []struct { + name string + base Word + offset int + addr Word + rtReg int + threadId Word + }{ + {name: "Aligned addr", base: 0x01, offset: 0x0137, addr: 0x0138, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, offset=1", base: 0x01, offset: 0x0100, addr: 0x0101, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, offset=2", base: 0x02, offset: 0x0100, addr: 0x0102, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, offset=3", base: 0x03, offset: 0x0100, addr: 0x0103, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, offset=4", base: 0x04, offset: 0x0100, addr: 0x0104, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, offset=5", base: 0x05, offset: 0x0100, addr: 0x0105, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, offset=6", base: 0x06, offset: 0x0100, addr: 0x0106, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, offset=7", base: 0x07, offset: 0x0100, addr: 0x0107, rtReg: 5, threadId: 4}, + {name: "Aligned addr, signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, rtReg: 5, threadId: 4}, + {name: "Aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF37, addr: 0x0FFF_FF38, rtReg: 5, threadId: 4}, + {name: "Return register set to 0", base: 0x01, offset: 0x0138, addr: 0x0139, rtReg: 0, threadId: 4}, + {name: "Zero valued ll args", base: 0x0, offset: 0x0, rtReg: 5, threadId: 0}, + } + for i, c := range cases { + for _, v := range llVariations { + tName := fmt.Sprintf("%v (%v)", c.name, v.name) + t.Run(tName, func(t *testing.T) { + effAddr := arch.AddressMask & c.addr + + // Setup + rtReg := c.rtReg + baseReg := 6 + insn := uint32((0b11_1100 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm, state, contracts := setup(t, i, nil) + mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) + step := state.GetStep() + + // Define LL-related params + var llAddress, llOwnerThread Word + if v.matchAddr { + llAddress = c.addr + } else { + llAddress = c.addr + 1 + } + if v.matchThreadId { + llOwnerThread = c.threadId + } else { + llOwnerThread = c.threadId + 1 + } + + // Setup state + state.GetCurrentThread().ThreadId = c.threadId + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[baseReg] = c.base + state.GetRegistersRef()[rtReg] = value + state.LLReservationStatus = v.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + var retVal Word + if v.shouldSucceed { + retVal = 1 + expected.ExpectMemoryWordWrite(effAddr, value) + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } else { + retVal = 0 + } + if rtReg != 0 { + expected.ActiveThread().Registers[rtReg] = retVal + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } + } +} + +func TestEVM_MT_SysRead_Preimage64(t *testing.T) { + preimageValue := make([]byte, 0, 8) + preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78) + preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32) + prestateMem := Word(0xEE_EE_EE_EE_FF_FF_FF_FF) + cases := []testMTSysReadPreimageTestCase{ + {name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_EE_EE_EE_FF_FF_FF_FF}, + {name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_EE_EE_FF_FF_FF_FF}, + {name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_EE_FF_FF_FF_FF}, + {name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_FF_FF_FF_FF}, + {name: "Aligned addr, write 5 byte", addr: 0x00_00_FF_00, count: 5, writeLen: 5, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_FF_FF_FF}, + {name: "Aligned addr, write 6 byte", addr: 0x00_00_FF_00, count: 6, writeLen: 6, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_FF_FF}, + {name: "Aligned addr, write 7 byte", addr: 0x00_00_FF_00, count: 7, writeLen: 7, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_54_FF}, + {name: "Aligned addr, write 8 byte", addr: 0x00_00_FF_00, count: 8, writeLen: 8, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_54_32}, + + {name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_EE_EE_FF_FF_FF_FF}, + {name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: prestateMem, postateMem: 0xEE_34_56_EE_FF_FF_FF_FF}, + {name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_FF_FF_FF_FF}, + {name: "1-byte misaligned addr, write 4 byte", addr: 0x00_00_FF_01, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_FF_FF_FF}, + {name: "1-byte misaligned addr, write 5 byte", addr: 0x00_00_FF_01, count: 5, writeLen: 5, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_FF_FF}, + {name: "1-byte misaligned addr, write 6 byte", addr: 0x00_00_FF_01, count: 6, writeLen: 6, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_76_FF}, + {name: "1-byte misaligned addr, write 7 byte", addr: 0x00_00_FF_01, count: 7, writeLen: 7, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_76_54}, + + {name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_12_EE_FF_FF_FF_FF}, + {name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: prestateMem, postateMem: 0xEE_EE_98_76_FF_FF_FF_FF}, + {name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 3, writeLen: 3, preimageOffset: 12, prestateMem: prestateMem, postateMem: 0xEE_EE_98_76_54_FF_FF_FF}, + {name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 4, writeLen: 4, preimageOffset: 12, prestateMem: prestateMem, postateMem: 0xEE_EE_98_76_54_32_FF_FF}, + + {name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_12_FF_FF_FF_FF}, + {name: "4-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_04, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_12_FF_FF_FF}, + {name: "5-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_05, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_12_FF_FF}, + {name: "6-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_06, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_12_FF}, + {name: "7-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_07, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_12}, + + {name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_FF}, + {name: "Count greater than 8", addr: 0x00_00_FF_00, count: 15, writeLen: 8, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0x12_34_56_78_98_76_54_32}, + {name: "Count greater than 8, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 7, preimageOffset: 8, prestateMem: prestateMem, postateMem: 0xEE_12_34_56_78_98_76_54}, + {name: "Offset at last byte", addr: 0x00_00_FF_00, count: 8, writeLen: 1, preimageOffset: 15, prestateMem: prestateMem, postateMem: 0x32_EE_EE_EE_FF_FF_FF_FF}, + {name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_FF, shouldPanic: true}, + {name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: prestateMem, postateMem: 0xEE_EE_EE_EE_FF_FF_FF_FF, shouldPanic: true}, + } + testMTSysReadPreimage(t, preimageValue, cases) +} + +func TestEVM_MT_StoreOpsClearMemReservation64(t *testing.T) { + t.Parallel() + cases := []testMTStoreOpsClearMemReservationTestCase{ + {name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x78_FF_FF_FF_FF_FF_FF_FF}, + {name: "Store byte lower", opcode: 0b10_1000, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_78_FF_FF_FF}, + {name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x56_78_FF_FF_FF_FF_FF_FF}, + {name: "Store halfword lower", opcode: 0b10_1001, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_56_78_FF_FF}, + {name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x12_34_56_78_FF_FF_FF_FF}, + {name: "Store word left lower", opcode: 0b10_1010, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_12_34_56_78}, + {name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x12_34_56_78_FF_FF_FF_FF}, + {name: "Store word lower", opcode: 0b10_1011, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_12_34_56_78}, + {name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_00, offset: 0x10, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0x78_FF_FF_FF_FF_FF_FF_FF}, + {name: "Store word right lower", opcode: 0b10_1110, base: 0xFF_00_00_00, offset: 0x14, effAddr: 0xFF_00_00_10, preMem: ^Word(0), postMem: 0xFF_FF_FF_FF_78_FF_FF_FF}, + } + testMTStoreOpsClearMemReservation(t, cases) +} + +var NoopSyscalls64 = map[string]uint32{ + "SysMunmap": 5011, + "SysGetAffinity": 5196, + "SysMadvise": 5027, + "SysRtSigprocmask": 5014, + "SysSigaltstack": 5129, + "SysRtSigaction": 5013, + "SysPrlimit64": 5297, + "SysClose": 5003, + "SysPread64": 5016, + "SysStat": 5004, + "SysFstat": 5005, + //"SysFstat64": UndefinedSysNr, + "SysOpenAt": 5247, + "SysReadlink": 5087, + "SysReadlinkAt": 5257, + "SysIoctl": 5015, + "SysEpollCreate1": 5285, + "SysPipe2": 5287, + "SysEpollCtl": 5208, + "SysEpollPwait": 5272, + "SysGetRandom": 5313, + "SysUname": 5061, + //"SysStat64": UndefinedSysNr, + "SysGetuid": 5100, + "SysGetgid": 5102, + //"SysLlseek": UndefinedSysNr, + "SysMinCore": 5026, + "SysTgkill": 5225, + "SysGetRLimit": 5095, + "SysLseek": 5008, + "SysSetITimer": 5036, + "SysTimerCreate": 5216, + "SysTimerSetTime": 5217, + "SysTimerDelete": 5220, +} + +func TestEVM_NoopSyscall64(t *testing.T) { + testNoopSyscall(t, NoopSyscalls64) +} + +func TestEVM_UnsupportedSyscall64(t *testing.T) { + t.Parallel() + + var noopSyscallNums = maps.Values(NoopSyscalls64) + var SupportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} + unsupportedSyscalls := make([]uint32, 0, 400) + for i := 5000; i < 5400; i++ { + candidate := uint32(i) + if slices.Contains(SupportedSyscalls, candidate) || slices.Contains(noopSyscallNums, candidate) { + continue + } + unsupportedSyscalls = append(unsupportedSyscalls, candidate) + } + + testUnsupportedSyscall(t, unsupportedSyscalls) +} diff --git a/cannon/mipsevm/tests/evm_multithreaded_test.go b/cannon/mipsevm/tests/evm_multithreaded_test.go index 21807c59f3f19..1353932dd71fc 100644 --- a/cannon/mipsevm/tests/evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded_test.go @@ -1,3 +1,4 @@ +// These tests target architectures that are 64-bit or larger package tests import ( @@ -7,9 +8,7 @@ import ( "slices" "testing" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -19,27 +18,33 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" - preimage "github.com/ethereum-optimism/optimism/op-preimage" ) type Word = arch.Word func TestEVM_MT_LL(t *testing.T) { - var tracer *tracing.Hooks + // Set up some test values that will be reused + posValue := uint64(0xAAAA_BBBB_1122_3344) + posValueRet := uint64(0x1122_3344) + negValue := uint64(0x1111_1111_8877_6655) + negRetValue := uint64(0xFFFF_FFFF_8877_6655) // Sign extended version of negValue + // Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit cases := []struct { - name string - base Word - offset int - value Word - effAddr Word - rtReg int + name string + base uint64 + offset int + expectedAddr uint64 + memValue uint64 + retVal uint64 + rtReg int }{ - {name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5}, - {name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5}, - {name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5}, - {name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5}, - {name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0}, + {name: "Aligned addr", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, memValue: posValue, retVal: posValueRet, rtReg: 5}, + {name: "Aligned addr, negative value", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, memValue: negValue, retVal: negRetValue, rtReg: 5}, + {name: "Aligned addr, addr signed extended", base: 0x01, offset: 0xFF33, expectedAddr: 0xFFFF_FFFF_FFFF_FF34, memValue: posValue, retVal: posValueRet, rtReg: 5}, + {name: "Unaligned addr", base: 0xFF12_0001, offset: 0x3405, expectedAddr: 0xFF12_3406, memValue: posValue, retVal: posValueRet, rtReg: 5}, + {name: "Unaligned addr, addr sign extended w overflow", base: 0xFF12_0001, offset: 0x8405, expectedAddr: 0xFF11_8406, memValue: posValue, retVal: posValueRet, rtReg: 5}, + {name: "Return register set to 0", base: 0xFF12_0001, offset: 0x7404, expectedAddr: 0xFF12_7405, memValue: posValue, retVal: 0, rtReg: 0}, } for i, c := range cases { for _, withExistingReservation := range []bool{true, false} { @@ -47,23 +52,20 @@ func TestEVM_MT_LL(t *testing.T) { t.Run(tName, func(t *testing.T) { rtReg := c.rtReg baseReg := 6 - pc := Word(0x44) insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil) + goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40)) step := state.GetStep() // Set up state - state.GetCurrentThread().Cpu.PC = pc - state.GetCurrentThread().Cpu.NextPC = pc + 4 - state.GetMemory().SetUint32(pc, insn) - state.GetMemory().SetWord(c.effAddr, c.value) - state.GetRegistersRef()[baseReg] = c.base + testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), c.memValue) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[baseReg] = Word(c.base) if withExistingReservation { - state.LLReservationActive = true - state.LLAddress = c.effAddr + Word(4) + state.LLReservationStatus = multithreaded.LLStatusActive32bit + state.LLAddress = Word(c.expectedAddr + 1) state.LLOwnerThread = 123 } else { - state.LLReservationActive = false + state.LLReservationStatus = multithreaded.LLStatusNone state.LLAddress = 0 state.LLOwnerThread = 0 } @@ -71,11 +73,11 @@ func TestEVM_MT_LL(t *testing.T) { // Set up expectations expected := mttestutil.NewExpectedMTState(state) expected.ExpectStep() - expected.LLReservationActive = true - expected.LLAddress = c.effAddr + expected.LLReservationStatus = multithreaded.LLStatusActive32bit + expected.LLAddress = Word(c.expectedAddr) expected.LLOwnerThread = state.GetCurrentThread().ThreadId if rtReg != 0 { - expected.ActiveThread().Registers[rtReg] = c.value + expected.ActiveThread().Registers[rtReg] = Word(c.retVal) } stepWitness, err := goVm.Step(true) @@ -83,44 +85,46 @@ func TestEVM_MT_LL(t *testing.T) { // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } } func TestEVM_MT_SC(t *testing.T) { - var tracer *tracing.Hooks + // Set up some test values that will be reused + memValue := uint64(0x1122_3344_5566_7788) llVariations := []struct { name string - llReservationActive bool + llReservationStatus multithreaded.LLReservationStatus matchThreadId bool - matchEffAddr bool + matchAddr bool shouldSucceed bool }{ - {name: "should succeed", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldSucceed: true}, - {name: "mismatch addr", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldSucceed: false}, - {name: "mismatched thread", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldSucceed: false}, - {name: "mismatched addr & thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldSucceed: false}, - {name: "no active reservation", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldSucceed: false}, + {name: "should succeed", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: true, shouldSucceed: true}, + {name: "mismatch thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: true, shouldSucceed: false}, + {name: "mismatched addr", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: false, shouldSucceed: false}, + {name: "mismatched addr & thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: false, shouldSucceed: false}, + {name: "mismatched status", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: true, shouldSucceed: false}, + {name: "no active reservation", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchAddr: true, shouldSucceed: false}, } + // Note: Some parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit cases := []struct { - name string - base Word - offset int - value Word - effAddr Word - rtReg int - threadId Word + name string + base Word + offset int + expectedAddr uint64 + storeValue uint32 + rtReg int + threadId Word }{ - {name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5, threadId: 4}, - {name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5, threadId: 4}, - {name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5, threadId: 4}, - {name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5, threadId: 4}, - {name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0, threadId: 4}, - {name: "Zero valued ll args", base: 0x00_00_00_00, offset: 0x0, value: 0xABCD, effAddr: 0x00_00_00_00, rtReg: 5, threadId: 0}, + {name: "Aligned addr", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4}, + {name: "Aligned addr, signed extended", base: 0x01, offset: 0xFF33, expectedAddr: 0xFFFF_FFFF_FFFF_FF34, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4}, + {name: "Unaligned addr", base: 0xFF12_0001, offset: 0x3404, expectedAddr: 0xFF12_3405, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4}, + {name: "Unaligned addr, sign extended w overflow", base: 0xFF12_0001, offset: 0x8404, expectedAddr: 0xFF_11_8405, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4}, + {name: "Return register set to 0", base: 0xFF12_0001, offset: 0x7403, expectedAddr: 0xFF12_7404, storeValue: 0xAABB_CCDD, rtReg: 0, threadId: 4}, } for i, c := range cases { for _, v := range llVariations { @@ -128,18 +132,17 @@ func TestEVM_MT_SC(t *testing.T) { t.Run(tName, func(t *testing.T) { rtReg := c.rtReg baseReg := 6 - pc := Word(0x44) insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) goVm, state, contracts := setup(t, i, nil) - mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1) + mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40)) step := state.GetStep() // Define LL-related params var llAddress, llOwnerThread Word - if v.matchEffAddr { - llAddress = c.effAddr + if v.matchAddr { + llAddress = Word(c.expectedAddr) } else { - llAddress = c.effAddr + 4 + llAddress = Word(c.expectedAddr) + 1 } if v.matchThreadId { llOwnerThread = c.threadId @@ -148,13 +151,12 @@ func TestEVM_MT_SC(t *testing.T) { } // Setup state + testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), memValue) state.GetCurrentThread().ThreadId = c.threadId - state.GetCurrentThread().Cpu.PC = pc - state.GetCurrentThread().Cpu.NextPC = pc + 4 - state.GetMemory().SetUint32(pc, insn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) state.GetRegistersRef()[baseReg] = c.base - state.GetRegistersRef()[rtReg] = c.value - state.LLReservationActive = v.llReservationActive + state.GetRegistersRef()[rtReg] = Word(c.storeValue) + state.LLReservationStatus = v.llReservationStatus state.LLAddress = llAddress state.LLOwnerThread = llOwnerThread @@ -164,8 +166,8 @@ func TestEVM_MT_SC(t *testing.T) { var retVal Word if v.shouldSucceed { retVal = 1 - expected.ExpectMemoryWordWrite(c.effAddr, c.value) - expected.LLReservationActive = false + expected.ExpectMemoryWriteUint32(t, Word(c.expectedAddr), c.storeValue) + expected.LLReservationStatus = multithreaded.LLStatusNone expected.LLAddress = 0 expected.LLOwnerThread = 0 } else { @@ -180,44 +182,20 @@ func TestEVM_MT_SC(t *testing.T) { // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } } -func TestEVM_MT_SysRead_Preimage(t *testing.T) { - var tracer *tracing.Hooks +func TestEVM_MT_SysRead_Preimage32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_MT_SysRead_Preimage64") + t.Parallel() preimageValue := make([]byte, 0, 8) preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78) preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32) - - llVariations := []struct { - name string - llReservationActive bool - matchThreadId bool - matchEffAddr bool - shouldClearReservation bool - }{ - {name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, - {name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true}, - {name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, - {name: "mismatched reservation", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false}, - {name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, - {name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, - } - - cases := []struct { - name string - addr Word - count Word - writeLen Word - preimageOffset Word - prestateMem uint32 - postateMem uint32 - shouldPanic bool - }{ + cases := []testMTSysReadPreimageTestCase{ {name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF}, {name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF}, {name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF}, @@ -235,164 +213,26 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) { {name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true}, {name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true}, } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - effAddr := arch.AddressMask & c.addr - preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey() - oracle := testutil.StaticOracle(t, preimageValue) - goVm, state, contracts := setup(t, i, oracle) - step := state.GetStep() - // Define LL-related params - var llAddress, llOwnerThread Word - if v.matchEffAddr { - llAddress = effAddr - } else { - llAddress = effAddr + 4 - } - if v.matchThreadId { - llOwnerThread = state.GetCurrentThread().ThreadId - } else { - llOwnerThread = state.GetCurrentThread().ThreadId + 1 - } - - // Set up state - state.PreimageKey = preimageKey - state.PreimageOffset = c.preimageOffset - state.GetRegistersRef()[2] = arch.SysRead - state.GetRegistersRef()[4] = exec.FdPreimageRead - state.GetRegistersRef()[5] = c.addr - state.GetRegistersRef()[6] = c.count - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) - state.LLReservationActive = v.llReservationActive - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - state.GetMemory().SetUint32(effAddr, c.prestateMem) - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = c.writeLen - expected.ActiveThread().Registers[7] = 0 // no error - expected.PreimageOffset += c.writeLen - expected.ExpectMemoryWrite(effAddr, c.postateMem) - if v.shouldClearReservation { - expected.LLReservationActive = false - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } - - if c.shouldPanic { - require.Panics(t, func() { _, _ = goVm.Step(true) }) - testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, contracts, tracer) - } else { - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) - } - }) - } - } + testMTSysReadPreimage(t, preimageValue, cases) } -func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) { - var tracer *tracing.Hooks - - llVariations := []struct { - name string - llReservationActive bool - matchThreadId bool - matchEffAddr bool - shouldClearReservation bool - }{ - {name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, - {name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true}, - {name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, - {name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false}, - {name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, - {name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, - } - - pc := Word(0x04) - rt := Word(0x12_34_56_78) - baseReg := 5 - rtReg := 6 - cases := []struct { - name string - opcode int - offset int - base Word - effAddr Word - preMem uint32 - postMem uint32 - }{ - {name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, - {name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x56_78_FF_FF}, - {name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, - {name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, - {name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, - } - for i, c := range cases { - for _, v := range llVariations { - tName := fmt.Sprintf("%v (%v)", c.name, v.name) - t.Run(tName, func(t *testing.T) { - insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) - goVm, state, contracts := setup(t, i, nil) - step := state.GetStep() - - // Define LL-related params - var llAddress, llOwnerThread Word - if v.matchEffAddr { - llAddress = c.effAddr - } else { - llAddress = c.effAddr + 4 - } - if v.matchThreadId { - llOwnerThread = state.GetCurrentThread().ThreadId - } else { - llOwnerThread = state.GetCurrentThread().ThreadId + 1 - } - - // Setup state - state.GetCurrentThread().Cpu.PC = pc - state.GetCurrentThread().Cpu.NextPC = pc + 4 - state.GetRegistersRef()[rtReg] = rt - state.GetRegistersRef()[baseReg] = c.base - state.GetMemory().SetUint32(state.GetPC(), insn) - state.GetMemory().SetUint32(c.effAddr, c.preMem) - state.LLReservationActive = v.llReservationActive - state.LLAddress = llAddress - state.LLOwnerThread = llOwnerThread - - // Setup expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ExpectMemoryWrite(c.effAddr, c.postMem) - if v.shouldClearReservation { - expected.LLReservationActive = false - expected.LLAddress = 0 - expected.LLOwnerThread = 0 - } - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) - }) - } +func TestEVM_MT_StoreOpsClearMemReservation32(t *testing.T) { + t.Parallel() + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_MT_StoreOpsClearMemReservation64") + + cases := []testMTStoreOpsClearMemReservationTestCase{ + {name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, + {name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x56_78_FF_FF}, + {name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, + {name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78}, + {name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_04, offset: 0x08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF}, } + testMTStoreOpsClearMemReservation(t, cases) } func TestEVM_SysClone_FlagHandling(t *testing.T) { contracts := testutil.TestContractsSetup(t, testutil.MipsMultithreaded) - var tracer *tracing.Hooks cases := []struct { name string @@ -413,45 +253,37 @@ func TestEVM_SysClone_FlagHandling(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { state := multithreaded.CreateEmptyState() - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysClone // Set syscall number state.GetRegistersRef()[4] = c.flags // Set first argument curStep := state.Step var err error var stepWitness *mipsevm.StepWitness - us := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil, nil) + goVm := multithreaded.NewInstrumentedState(state, nil, os.Stdout, os.Stderr, nil, nil) if !c.valid { // The VM should exit - stepWitness, err = us.Step(true) + stepWitness, err = goVm.Step(true) require.NoError(t, err) require.Equal(t, curStep+1, state.GetStep()) - require.Equal(t, true, us.GetState().GetExited()) - require.Equal(t, uint8(mipsevm.VMStatusPanic), us.GetState().GetExitCode()) + require.Equal(t, true, goVm.GetState().GetExited()) + require.Equal(t, uint8(mipsevm.VMStatusPanic), goVm.GetState().GetExitCode()) require.Equal(t, 1, state.ThreadCount()) } else { - stepWitness, err = us.Step(true) + stepWitness, err = goVm.Step(true) require.NoError(t, err) require.Equal(t, curStep+1, state.GetStep()) - require.Equal(t, false, us.GetState().GetExited()) - require.Equal(t, uint8(0), us.GetState().GetExitCode()) + require.Equal(t, false, goVm.GetState().GetExited()) + require.Equal(t, uint8(0), goVm.GetState().GetExitCode()) require.Equal(t, 2, state.ThreadCount()) } - evm := testutil.NewMIPSEVM(contracts) - evm.SetTracer(tracer) - testutil.LogStepFailureAtCleanup(t, evm) - - evmPost := evm.Step(t, stepWitness, curStep, multithreaded.GetStateHashFn()) - goPost, _ := us.GetState().EncodeWitness() - require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), - "mipsevm produced different state than EVM") + testutil.ValidateEVM(t, stepWitness, curStep, goVm, multithreaded.GetStateHashFn(), contracts) }) } } func TestEVM_SysClone_Successful(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string traverseRight bool @@ -466,7 +298,7 @@ func TestEVM_SysClone_Successful(t *testing.T) { goVm, state, contracts := setup(t, i, nil) mttestutil.InitializeSingleThread(i*333, state, c.traverseRight) - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysClone // the syscall number state.GetRegistersRef()[4] = exec.ValidCloneFlags // a0 - first argument, clone flags state.GetRegistersRef()[5] = stackPtr // a1 - the stack pointer @@ -508,13 +340,12 @@ func TestEVM_SysClone_Successful(t *testing.T) { activeStack, inactiveStack := mttestutil.GetThreadStacks(state) require.Equal(t, 2, len(activeStack)) require.Equal(t, 0, len(inactiveStack)) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } func TestEVM_SysGetTID(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string threadId Word @@ -529,7 +360,7 @@ func TestEVM_SysGetTID(t *testing.T) { mttestutil.InitializeSingleThread(i*789, state, false) state.GetCurrentThread().ThreadId = c.threadId - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysGetTID // Set syscall number step := state.Step @@ -547,13 +378,12 @@ func TestEVM_SysGetTID(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } func TestEVM_SysExit(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string threadCount int @@ -572,7 +402,7 @@ func TestEVM_SysExit(t *testing.T) { goVm, state, contracts := setup(t, i*133, nil) mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0) - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysExit // Set syscall number state.GetRegistersRef()[4] = Word(exitCode) // The first argument (exit code) step := state.Step @@ -596,13 +426,12 @@ func TestEVM_SysExit(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } func TestEVM_PopExitedThread(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string traverseRight bool @@ -648,45 +477,44 @@ func TestEVM_PopExitedThread(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } func TestEVM_SysFutex_WaitPrivate(t *testing.T) { - var tracer *tracing.Hooks + // Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit cases := []struct { name string - addressParam Word - effAddr Word - targetValue Word - actualValue Word - timeout Word + addressParam uint64 + effAddr uint64 + targetValue uint64 + actualValue uint64 + timeout uint64 shouldFail bool shouldSetTimeout bool }{ - {name: "successful wait, no timeout", addressParam: 0x1234, effAddr: 0x1234, targetValue: 0x01, actualValue: 0x01}, - {name: "successful wait, no timeout, unaligned addr", addressParam: 0x1235, effAddr: 0x1234, targetValue: 0x01, actualValue: 0x01}, - {name: "memory mismatch, no timeout", addressParam: 0x1200, effAddr: 0x1200, targetValue: 0x01, actualValue: 0x02, shouldFail: true}, - {name: "memory mismatch, no timeout, unaligned", addressParam: 0x1203, effAddr: 0x1200, targetValue: 0x01, actualValue: 0x02, shouldFail: true}, - {name: "successful wait w timeout", addressParam: 0x1234, effAddr: 0x1234, targetValue: 0x01, actualValue: 0x01, timeout: 1000000, shouldSetTimeout: true}, - {name: "successful wait w timeout, unaligned", addressParam: 0x1232, effAddr: 0x1230, targetValue: 0x01, actualValue: 0x01, timeout: 1000000, shouldSetTimeout: true}, - {name: "memory mismatch w timeout", addressParam: 0x1200, effAddr: 0x1200, targetValue: 0x01, actualValue: 0x02, timeout: 2000000, shouldFail: true}, - {name: "memory mismatch w timeout, unaligned", addressParam: 0x120F, effAddr: 0x120C, targetValue: 0x01, actualValue: 0x02, timeout: 2000000, shouldFail: true}, + {name: "successful wait, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01}, + {name: "successful wait, no timeout, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_12_39, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01}, + {name: "memory mismatch, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, shouldFail: true}, + {name: "memory mismatch, no timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_03, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, shouldFail: true}, + {name: "successful wait w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true}, + {name: "successful wait w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_32, effAddr: 0xFF_FF_FF_FF_FF_FF_12_30, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true}, + {name: "memory mismatch w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, timeout: 2000000, shouldFail: true}, + {name: "memory mismatch w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_0F, effAddr: 0xFF_FF_FF_FF_FF_FF_12_10, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, timeout: 2000000, shouldFail: true}, } - for i, c := range cases { t.Run(c.name, func(t *testing.T) { goVm, state, contracts := setup(t, i*1234, nil) step := state.GetStep() - state.Memory.SetUint32(state.GetPC(), syscallInsn) - state.Memory.SetWord(c.effAddr, c.actualValue) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.Memory.SetWord(Word(c.effAddr), Word(c.actualValue)) state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number - state.GetRegistersRef()[4] = c.addressParam + state.GetRegistersRef()[4] = Word(c.addressParam) state.GetRegistersRef()[5] = exec.FutexWaitPrivate - state.GetRegistersRef()[6] = c.targetValue - state.GetRegistersRef()[7] = c.timeout + state.GetRegistersRef()[6] = Word(c.targetValue) + state.GetRegistersRef()[7] = Word(c.timeout) // Setup expectations expected := mttestutil.NewExpectedMTState(state) @@ -699,8 +527,8 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { expected.ActiveThread().Registers[7] = exec.MipsEAGAIN } else { // PC and return registers should not update on success, updates happen when wait completes - expected.ActiveThread().FutexAddr = c.effAddr - expected.ActiveThread().FutexVal = c.targetValue + expected.ActiveThread().FutexAddr = Word(c.effAddr) + expected.ActiveThread().FutexVal = Word(c.targetValue) expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout if c.shouldSetTimeout { expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1 @@ -708,52 +536,49 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { } // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) + stepWitness, err := goVm.Step(true) require.NoError(t, err) // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } func TestEVM_SysFutex_WakePrivate(t *testing.T) { - var tracer *tracing.Hooks + // Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit cases := []struct { name string - addressParam Word - effAddr Word + addressParam uint64 + effAddr uint64 activeThreadCount int inactiveThreadCount int traverseRight bool expectTraverseRight bool }{ - {name: "Traverse right", addressParam: 0x6700, effAddr: 0x6700, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, - {name: "Traverse right, unaligned addr", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, - {name: "Traverse right, no left threads", addressParam: 0x6784, effAddr: 0x6784, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse right, no left threads, unaligned addr", addressParam: 0x678E, effAddr: 0x678C, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse right, single thread", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse right, single thread, unaligned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, - {name: "Traverse left", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, - {name: "Traverse left, unaliagned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, - {name: "Traverse left, switch directions", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, - {name: "Traverse left, switch directions, unaligned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, - {name: "Traverse left, single thread", addressParam: 0x6788, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, - {name: "Traverse left, single thread, unaligned", addressParam: 0x6789, effAddr: 0x6788, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse right", addressParam: 0xFF_FF_FF_FF_FF_FF_67_00, effAddr: 0xFF_FF_FF_FF_FF_FF_67_00, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, + {name: "Traverse right, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, + {name: "Traverse right, no left threads", addressParam: 0xFF_FF_FF_FF_FF_FF_67_84, effAddr: 0xFF_FF_FF_FF_FF_FF_67_84, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse right, no left threads, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_67_8E, effAddr: 0xFF_FF_FF_FF_FF_FF_67_8C, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse right, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse right, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, + {name: "Traverse left", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, + {name: "Traverse left, unaliagned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, + {name: "Traverse left, switch directions", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse left, switch directions, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse left, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, + {name: "Traverse left, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, } - for i, c := range cases { t.Run(c.name, func(t *testing.T) { goVm, state, contracts := setup(t, i*1122, nil) mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount) step := state.Step - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number - state.GetRegistersRef()[4] = c.addressParam + state.GetRegistersRef()[4] = Word(c.addressParam) state.GetRegistersRef()[5] = exec.FutexWakePrivate // Set up post-state expectations @@ -761,7 +586,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { expected.ExpectStep() expected.ActiveThread().Registers[2] = 0 expected.ActiveThread().Registers[7] = 0 - expected.Wakeup = c.effAddr + expected.Wakeup = Word(c.effAddr) & arch.AddressMask // aligned for 32 and 64-bit compatibility expected.ExpectPreemption(state) expected.TraverseRight = c.expectTraverseRight if c.traverseRight != c.expectTraverseRight { @@ -771,21 +596,18 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { } // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) + stepWitness, err := goVm.Step(true) require.NoError(t, err) // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } + } func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { - var tracer *tracing.Hooks - // From: https://github.com/torvalds/linux/blob/5be63fc19fcaa4c236b307420483578a56986a37/include/uapi/linux/futex.h const FUTEX_PRIVATE_FLAG = 128 const FUTEX_WAIT = 0 @@ -836,7 +658,7 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { goVm, state, contracts := setup(t, int(op), nil) step := state.GetStep() - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number state.GetRegistersRef()[5] = op @@ -857,7 +679,7 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } @@ -871,7 +693,6 @@ func TestEVM_SysNanosleep(t *testing.T) { } func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { - var tracer *tracing.Hooks cases := []struct { name string traverseRight bool @@ -891,7 +712,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { goVm, state, contracts := setup(t, i*789, nil) mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads) - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = Word(syscallNum) // Set syscall number step := state.Step @@ -910,18 +731,16 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } } func TestEVM_SysOpen(t *testing.T) { - var tracer *tracing.Hooks - goVm, state, contracts := setup(t, 5512, nil) - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysOpen // Set syscall number step := state.Step @@ -939,14 +758,13 @@ func TestEVM_SysOpen(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) } func TestEVM_SysGetPID(t *testing.T) { - var tracer *tracing.Hooks goVm, state, contracts := setup(t, 1929, nil) - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysGetpid // Set syscall number step := state.Step @@ -964,7 +782,7 @@ func TestEVM_SysGetPID(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) } func TestEVM_SysClockGettimeMonotonic(t *testing.T) { @@ -976,25 +794,25 @@ func TestEVM_SysClockGettimeRealtime(t *testing.T) { } func testEVM_SysClockGettime(t *testing.T, clkid Word) { - var tracer *tracing.Hooks - llVariations := []struct { name string - llReservationActive bool + llReservationStatus multithreaded.LLReservationStatus matchThreadId bool matchEffAddr bool matchEffAddr2 bool shouldClearReservation bool }{ - {name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, - {name: "matching reservation, 2nd word", llReservationActive: true, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true}, - {name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true}, - {name: "matching reservation, diff thread, 2nd word", llReservationActive: true, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true}, - {name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, - {name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false}, - {name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, - {name: "no reservation, matching addr2", llReservationActive: false, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true}, - {name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, + {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, + {name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, + {name: "matching reservation, 2nd word", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true}, + {name: "matching reservation, 2nd word, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true}, + {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true}, + {name: "matching reservation, diff thread, 2nd word", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true}, + {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, + {name: "mismatched reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false}, + {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, + {name: "no reservation, matching addr2", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true}, + {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, } cases := []struct { @@ -1011,7 +829,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { goVm, state, contracts := setup(t, 2101, nil) mttestutil.InitializeSingleThread(2101+i, state, i%2 == 1) effAddr := c.timespecAddr & arch.AddressMask - effAddr2 := effAddr + 4 + effAddr2 := effAddr + arch.WordSizeBytes step := state.Step // Define LL-related params @@ -1029,11 +847,11 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { llOwnerThread = state.GetCurrentThread().ThreadId + 1 } - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number state.GetRegistersRef()[4] = clkid // a0 state.GetRegistersRef()[5] = c.timespecAddr // a1 - state.LLReservationActive = v.llReservationActive + state.LLReservationStatus = v.llReservationStatus state.LLAddress = llAddress state.LLOwnerThread = llOwnerThread @@ -1050,7 +868,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { expected.ExpectMemoryWordWrite(effAddr, secs) expected.ExpectMemoryWordWrite(effAddr2, nsecs) if v.shouldClearReservation { - expected.LLReservationActive = false + expected.LLReservationStatus = multithreaded.LLStatusNone expected.LLAddress = 0 expected.LLOwnerThread = 0 } @@ -1062,18 +880,17 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } } func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) { - var tracer *tracing.Hooks goVm, state, contracts := setup(t, 2101, nil) timespecAddr := Word(0x1000) - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number state.GetRegistersRef()[4] = 0xDEAD // a0 - invalid clockid state.GetRegistersRef()[5] = timespecAddr // a1 @@ -1091,7 +908,7 @@ func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) } var NoopSyscalls = map[string]uint32{ @@ -1103,6 +920,7 @@ var NoopSyscalls = map[string]uint32{ "SysPrlimit64": 4338, "SysClose": 4006, "SysPread64": 4200, + "SysStat": 4106, "SysFstat": 4108, "SysFstat64": 4215, "SysOpenAt": 4288, @@ -1121,6 +939,8 @@ var NoopSyscalls = map[string]uint32{ "SysLlseek": 4140, "SysMinCore": 4217, "SysTgkill": 4266, + "SysGetRLimit": 4076, + "SysLseek": 4019, "SysMunmap": 4091, "SysSetITimer": 4104, "SysTimerCreate": 4257, @@ -1128,73 +948,63 @@ var NoopSyscalls = map[string]uint32{ "SysTimerDelete": 4261, } -func TestEVM_NoopSyscall(t *testing.T) { - var tracer *tracing.Hooks - for noopName, noopVal := range NoopSyscalls { - t.Run(noopName, func(t *testing.T) { - goVm, state, contracts := setup(t, int(noopVal), nil) - - state.Memory.SetUint32(state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = Word(noopVal) // Set syscall number - step := state.Step - - // Set up post-state expectations - expected := mttestutil.NewExpectedMTState(state) - expected.ExpectStep() - expected.ActiveThread().Registers[2] = 0 - expected.ActiveThread().Registers[7] = 0 - - // State transition - var err error - var stepWitness *mipsevm.StepWitness - stepWitness, err = goVm.Step(true) - require.NoError(t, err) - - // Validate post-state - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) - }) - - } +func TestEVM_NoopSyscall32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_NoopSyscall64") + testNoopSyscall(t, NoopSyscalls) } -func TestEVM_UnsupportedSyscall(t *testing.T) { +func TestEVM_UnsupportedSyscall32(t *testing.T) { + testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_UnsupportedSyscall64") t.Parallel() - var tracer *tracing.Hooks - var NoopSyscallNums = maps.Values(NoopSyscalls) - var SupportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} + var noopSyscallNums = maps.Values(NoopSyscalls) + var supportedSyscalls = []uint32{arch.SysMmap, arch.SysBrk, arch.SysClone, arch.SysExitGroup, arch.SysRead, arch.SysWrite, arch.SysFcntl, arch.SysExit, arch.SysSchedYield, arch.SysGetTID, arch.SysFutex, arch.SysOpen, arch.SysNanosleep, arch.SysClockGetTime, arch.SysGetpid} unsupportedSyscalls := make([]uint32, 0, 400) for i := 4000; i < 4400; i++ { candidate := uint32(i) - if slices.Contains(SupportedSyscalls, candidate) || slices.Contains(NoopSyscallNums, candidate) { + if slices.Contains(supportedSyscalls, candidate) || slices.Contains(noopSyscallNums, candidate) { continue } unsupportedSyscalls = append(unsupportedSyscalls, candidate) } - for i, syscallNum := range unsupportedSyscalls { - testName := fmt.Sprintf("Unsupported syscallNum %v", syscallNum) - i := i - syscallNum := syscallNum - t.Run(testName, func(t *testing.T) { - t.Parallel() - goVm, state, contracts := setup(t, i*3434, nil) - // Setup basic getThreadId syscall instruction - state.Memory.SetUint32(state.GetPC(), syscallInsn) - state.GetRegistersRef()[2] = Word(syscallNum) - proofData := multiThreadedProofGenerator(t, state) - // Set up post-state expectations - require.Panics(t, func() { _, _ = goVm.Step(true) }) + testUnsupportedSyscall(t, unsupportedSyscalls) +} - errorMessage := "MIPS2: unimplemented syscall" - testutil.AssertEVMReverts(t, state, contracts, tracer, proofData, errorMessage) - }) +func TestEVM_EmptyThreadStacks(t *testing.T) { + t.Parallel() + var tracer *tracing.Hooks + + cases := []struct { + name string + otherStackSize int + traverseRight bool + }{ + {name: "Traverse right with empty stacks", otherStackSize: 0, traverseRight: true}, + {name: "Traverse left with empty stacks", otherStackSize: 0, traverseRight: false}, + {name: "Traverse right with one non-empty stack on the other side", otherStackSize: 1, traverseRight: true}, + {name: "Traverse left with one non-empty stack on the other side", otherStackSize: 1, traverseRight: false}, + } + // Generate proof variations + proofVariations := GenerateEmptyThreadProofVariations(t) + + for i, c := range cases { + for _, proofCase := range proofVariations { + testName := fmt.Sprintf("%v (proofCase=%v)", c.name, proofCase.Name) + t.Run(testName, func(t *testing.T) { + goVm, state, contracts := setup(t, i*123, nil) + mttestutil.SetupThreads(int64(i*123), state, c.traverseRight, 0, c.otherStackSize) + + require.PanicsWithValue(t, "Active thread stack is empty", func() { _, _ = goVm.Step(false) }) + + errorMessage := "active thread stack is empty" + testutil.AssertEVMReverts(t, state, contracts, tracer, proofCase.Proof, testutil.CreateErrorStringMatcher(errorMessage)) + }) + } } } func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string step uint64 @@ -1271,7 +1081,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, c.step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, c.step, goVm, multithreaded.GetStateHashFn(), contracts) }) } @@ -1279,7 +1089,6 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { } func TestEVM_NormalTraversal_Full(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string threadCount int @@ -1321,7 +1130,7 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) } // We should be back to the original state with only a few modifications @@ -1336,7 +1145,6 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { func TestEVM_WakeupTraversalStep(t *testing.T) { addr := Word(0x1234) wakeupVal := Word(0x999) - var tracer *tracing.Hooks cases := []struct { name string wakeupAddr Word @@ -1404,13 +1212,12 @@ func TestEVM_WakeupTraversalStep(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } func TestEVM_WakeupTraversal_Full(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string threadCount int @@ -1450,7 +1257,7 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) } // We should be back to the original state with only a few modifications @@ -1462,8 +1269,80 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) { } } +func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) { + addr := Word(0x1234) + wakeupVal := Word(0x999) + cases := []struct { + name string + wakeupAddr Word + futexAddr Word + targetVal Word + traverseRight bool + activeStackSize int + otherStackSize int + exitedThreadIdx []int + shouldClearWakeup bool + shouldPreempt bool + activeThreadFutexAddr Word + activeThreadFutexVal Word + }{ + {name: "Wakeable thread exists among exited threads", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal + 1, traverseRight: false, activeStackSize: 3, otherStackSize: 1, exitedThreadIdx: []int{2}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 8, activeThreadFutexVal: wakeupVal + 2}, + {name: "All threads exited", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: false, activeStackSize: 3, otherStackSize: 0, exitedThreadIdx: []int{1, 2}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 16, activeThreadFutexVal: wakeupVal + 3}, + {name: "Exited threads, no matching futex", wakeupAddr: addr, futexAddr: addr + 4, targetVal: wakeupVal, traverseRight: false, activeStackSize: 2, otherStackSize: 1, exitedThreadIdx: []int{}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 24, activeThreadFutexVal: wakeupVal + 4}, + {name: "Matching addr, not wakeable, with exited threads", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: true, activeStackSize: 3, otherStackSize: 0, exitedThreadIdx: []int{1}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 32, activeThreadFutexVal: wakeupVal + 5}, + {name: "Non-waiting threads with exited threads", wakeupAddr: addr, futexAddr: exec.FutexEmptyAddr, targetVal: 0, traverseRight: false, activeStackSize: 2, otherStackSize: 1, exitedThreadIdx: []int{}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 40, activeThreadFutexVal: wakeupVal + 6}, + } + + for i, c := range cases { + t.Run(c.name, func(t *testing.T) { + goVm, state, contracts := setup(t, i*1000, nil) + mttestutil.SetupThreads(int64(i*5000), state, c.traverseRight, c.activeStackSize, c.otherStackSize) + step := state.Step + + state.Wakeup = c.wakeupAddr + state.GetMemory().SetWord(c.wakeupAddr&arch.AddressMask, wakeupVal) + + threads := mttestutil.GetAllThreads(state) + for idx, thread := range threads { + if slices.Contains(c.exitedThreadIdx, idx) { + thread.Exited = true + } else { + thread.FutexAddr = c.futexAddr + thread.FutexVal = c.targetVal + thread.FutexTimeoutStep = exec.FutexNoTimeout + } + } + + activeThread := state.GetCurrentThread() + activeThread.Exited = true + + activeThread.FutexAddr = c.activeThreadFutexAddr + activeThread.FutexVal = c.activeThreadFutexVal + activeThread.FutexTimeoutStep = exec.FutexNoTimeout + + expected := mttestutil.NewExpectedMTState(state) + expected.Step += 1 + + if c.shouldClearWakeup { + expected.Wakeup = exec.FutexEmptyAddr + } + if c.shouldPreempt { + // Just preempt the current thread + expected.ExpectPreemption(state) + } + + // State transition + var err error + var stepWitness *mipsevm.StepWitness + stepWitness, err = goVm.Step(true) + require.NoError(t, err) + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } +} + func TestEVM_SchedQuantumThreshold(t *testing.T) { - var tracer *tracing.Hooks cases := []struct { name string stepsSinceLastContextSwitch uint64 @@ -1478,7 +1357,7 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { t.Run(c.name, func(t *testing.T) { goVm, state, contracts := setup(t, i*789, nil) // Setup basic getThreadId syscall instruction - state.Memory.SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysGetTID // Set syscall number state.StepsSinceLastContextSwitch = c.stepsSinceLastContextSwitch step := state.Step @@ -1503,14 +1382,15 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { // Validate post-state expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) }) } } -func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { +func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle, opts ...testutil.StateOption) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { v := GetMultiThreadedTestCase(t) - vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(randomSeed))) + allOpts := append([]testutil.StateOption{testutil.WithRandomization(int64(randomSeed))}, opts...) + vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), allOpts...) state := mttestutil.GetMtState(t, vm) return vm, state, v.Contracts diff --git a/cannon/mipsevm/tests/evm_singlethreaded_test.go b/cannon/mipsevm/tests/evm_singlethreaded_test.go index cf19a83a14cea..56c5e31c3463f 100644 --- a/cannon/mipsevm/tests/evm_singlethreaded_test.go +++ b/cannon/mipsevm/tests/evm_singlethreaded_test.go @@ -1,3 +1,6 @@ +//go:build !cannon64 +// +build !cannon64 + package tests import ( @@ -5,7 +8,6 @@ import ( "os" "testing" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -17,8 +19,6 @@ import ( ) func TestEVM_LL(t *testing.T) { - var tracer *tracing.Hooks - cases := []struct { name string base Word @@ -42,7 +42,7 @@ func TestEVM_LL(t *testing.T) { insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(pc), testutil.WithNextPC(pc+4)) state := goVm.GetState() - state.GetMemory().SetUint32(pc, insn) + testutil.StoreInstruction(state.GetMemory(), pc, insn) state.GetMemory().SetWord(c.effAddr, c.value) state.GetRegistersRef()[baseReg] = c.base step := state.GetStep() @@ -61,14 +61,12 @@ func TestEVM_LL(t *testing.T) { // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } func TestEVM_SC(t *testing.T) { - var tracer *tracing.Hooks - cases := []struct { name string base Word @@ -92,7 +90,7 @@ func TestEVM_SC(t *testing.T) { insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(pc), testutil.WithNextPC(pc+4)) state := goVm.GetState() - state.GetMemory().SetUint32(pc, insn) + testutil.StoreInstruction(state.GetMemory(), pc, insn) state.GetRegistersRef()[baseReg] = c.base state.GetRegistersRef()[rtReg] = c.value step := state.GetStep() @@ -103,7 +101,7 @@ func TestEVM_SC(t *testing.T) { expected.PC = pc + 4 expected.NextPC = pc + 8 expectedMemory := memory.NewMemory() - expectedMemory.SetUint32(pc, insn) + testutil.StoreInstruction(expectedMemory, pc, insn) expectedMemory.SetWord(c.effAddr, c.value) expected.MemoryRoot = expectedMemory.MerkleRoot() if rtReg != 0 { @@ -115,14 +113,12 @@ func TestEVM_SC(t *testing.T) { // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } } func TestEVM_SysRead_Preimage(t *testing.T) { - var tracer *tracing.Hooks - preimageValue := make([]byte, 0, 8) preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78) preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32) @@ -135,8 +131,8 @@ func TestEVM_SysRead_Preimage(t *testing.T) { count Word writeLen Word preimageOffset Word - prestateMem uint32 - postateMem uint32 + prestateMem Word + postateMem Word shouldPanic bool }{ {name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF}, @@ -170,8 +166,8 @@ func TestEVM_SysRead_Preimage(t *testing.T) { state.GetRegistersRef()[4] = exec.FdPreimageRead state.GetRegistersRef()[5] = c.addr state.GetRegistersRef()[6] = c.count - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) - state.GetMemory().SetUint32(effAddr, c.prestateMem) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) + state.GetMemory().SetWord(effAddr, c.prestateMem) // Setup expectations expected := testutil.NewExpectedState(state) @@ -179,18 +175,18 @@ func TestEVM_SysRead_Preimage(t *testing.T) { expected.Registers[2] = c.writeLen expected.Registers[7] = 0 // no error expected.PreimageOffset += c.writeLen - expected.ExpectMemoryWrite(effAddr, c.postateMem) + expected.ExpectMemoryWriteWord(effAddr, c.postateMem) if c.shouldPanic { require.Panics(t, func() { _, _ = goVm.Step(true) }) - testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts, tracer) + testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts) } else { stepWitness, err := goVm.Step(true) require.NoError(t, err) // Check expectations expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) } }) } diff --git a/cannon/mipsevm/tests/fuzz_evm_common64_test.go b/cannon/mipsevm/tests/fuzz_evm_common64_test.go new file mode 100644 index 0000000000000..d972c3a7ff260 --- /dev/null +++ b/cannon/mipsevm/tests/fuzz_evm_common64_test.go @@ -0,0 +1,134 @@ +//go:build cannon64 +// +build cannon64 + +package tests + +import ( + "os" + "testing" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" + "github.com/stretchr/testify/require" +) + +func FuzzStateConsistencyMulOp(f *testing.F) { + f.Add(int64(0x80_00_00_00), int64(0x80_00_00_00), int64(1)) + f.Add( + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)), + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)), + int64(1), + ) + f.Add( + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)), + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)), + int64(1), + ) + f.Add( + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)), + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)), + int64(1), + ) + + const opcode uint32 = 28 + const mulFunct uint32 = 0x2 + versions := GetMipsVersionTestCases(f) + f.Fuzz(func(t *testing.T, rs int64, rt int64, seed int64) { + for _, v := range versions { + t.Run(v.Name, func(t *testing.T) { + mulOpConsistencyCheck(t, versions, opcode, true, mulFunct, Word(rs), Word(rt), seed) + }) + } + }) +} + +func FuzzStateConsistencyMultOp(f *testing.F) { + f.Add(int64(0x80_00_00_00), int64(0x80_00_00_00), int64(1)) + f.Add( + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)), + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_11_22_33_44)), + int64(1), + ) + f.Add( + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)), + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_80_00_00_00)), + int64(1), + ) + f.Add( + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)), + testutil.ToSignedInteger(uint64(0xFF_FF_FF_FF_FF_FF_FF_FF)), + int64(1), + ) + + const multFunct uint32 = 0x18 + versions := GetMipsVersionTestCases(f) + f.Fuzz(func(t *testing.T, rs int64, rt int64, seed int64) { + mulOpConsistencyCheck(t, versions, 0, false, multFunct, Word(rs), Word(rt), seed) + }) +} + +func FuzzStateConsistencyMultuOp(f *testing.F) { + f.Add(uint64(0x80_00_00_00), uint64(0x80_00_00_00), int64(1)) + f.Add( + uint64(0xFF_FF_FF_FF_11_22_33_44), + uint64(0xFF_FF_FF_FF_11_22_33_44), + int64(1), + ) + f.Add( + uint64(0xFF_FF_FF_FF_80_00_00_00), + uint64(0xFF_FF_FF_FF_80_00_00_00), + int64(1), + ) + f.Add( + uint64(0xFF_FF_FF_FF_FF_FF_FF_FF), + uint64(0xFF_FF_FF_FF_FF_FF_FF_FF), + int64(1), + ) + + const multuFunct uint32 = 0x19 + versions := GetMipsVersionTestCases(f) + f.Fuzz(func(t *testing.T, rs uint64, rt uint64, seed int64) { + mulOpConsistencyCheck(t, versions, 0, false, multuFunct, rs, rt, seed) + }) +} + +func mulOpConsistencyCheck( + t *testing.T, versions []VersionedVMTestCase, + opcode uint32, expectRdReg bool, funct uint32, + rs Word, rt Word, seed int64) { + for _, v := range versions { + t.Run(v.Name, func(t *testing.T) { + rsReg := uint32(17) + rtReg := uint32(18) + rdReg := uint32(0) + if expectRdReg { + rdReg = 19 + } + + insn := opcode<<26 | rsReg<<21 | rtReg<<16 | rdReg<<11 | funct + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed), testutil.WithPCAndNextPC(0)) + state := goVm.GetState() + state.GetRegistersRef()[rsReg] = rs + state.GetRegistersRef()[rtReg] = rt + testutil.StoreInstruction(state.GetMemory(), 0, insn) + step := state.GetStep() + + // mere sanity checks + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // use the post-state rdReg or LO and HI just so we can run sanity checks + if expectRdReg { + expected.Registers[rdReg] = state.GetRegistersRef()[rdReg] + } else { + expected.LO = state.GetCpu().LO + expected.HI = state.GetCpu().HI + } + expected.Validate(t, state) + + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } +} diff --git a/cannon/mipsevm/tests/fuzz_evm_common_test.go b/cannon/mipsevm/tests/fuzz_evm_common_test.go index 8c7081456af86..c637cbbe19a0b 100644 --- a/cannon/mipsevm/tests/fuzz_evm_common_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_common_test.go @@ -2,7 +2,6 @@ package tests import ( "bytes" - "encoding/binary" "math" "os" "testing" @@ -28,7 +27,7 @@ func FuzzStateSyscallBrk(f *testing.F) { goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed)) state := goVm.GetState() state.GetRegistersRef()[2] = arch.SysBrk - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) step := state.GetStep() expected := testutil.NewExpectedState(state) @@ -43,7 +42,7 @@ func FuzzStateSyscallBrk(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) @@ -68,7 +67,7 @@ func FuzzStateSyscallMmap(f *testing.F) { state.GetRegistersRef()[2] = arch.SysMmap state.GetRegistersRef()[4] = addr state.GetRegistersRef()[5] = siz - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) expected := testutil.NewExpectedState(state) expected.Step += 1 @@ -98,7 +97,7 @@ func FuzzStateSyscallMmap(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) @@ -114,7 +113,7 @@ func FuzzStateSyscallExitGroup(f *testing.F) { state := goVm.GetState() state.GetRegistersRef()[2] = arch.SysExitGroup state.GetRegistersRef()[4] = Word(exitCode) - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) step := state.GetStep() expected := testutil.NewExpectedState(state) @@ -127,7 +126,7 @@ func FuzzStateSyscallExitGroup(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) @@ -144,7 +143,7 @@ func FuzzStateSyscallFcntl(f *testing.F) { state.GetRegistersRef()[2] = arch.SysFcntl state.GetRegistersRef()[4] = fd state.GetRegistersRef()[5] = cmd - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) step := state.GetStep() expected := testutil.NewExpectedState(state) @@ -158,7 +157,7 @@ func FuzzStateSyscallFcntl(f *testing.F) { expected.Registers[2] = 0 expected.Registers[7] = 0 default: - expected.Registers[2] = 0xFF_FF_FF_FF + expected.Registers[2] = ^Word(0) expected.Registers[7] = exec.MipsEBADF } } else if cmd == 3 { @@ -170,11 +169,11 @@ func FuzzStateSyscallFcntl(f *testing.F) { expected.Registers[2] = 1 expected.Registers[7] = 0 default: - expected.Registers[2] = 0xFF_FF_FF_FF + expected.Registers[2] = ^Word(0) expected.Registers[7] = exec.MipsEBADF } } else { - expected.Registers[2] = 0xFF_FF_FF_FF + expected.Registers[2] = ^Word(0) expected.Registers[7] = exec.MipsEINVAL } @@ -183,7 +182,7 @@ func FuzzStateSyscallFcntl(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) @@ -205,7 +204,7 @@ func FuzzStateHintRead(f *testing.F) { state.GetRegistersRef()[4] = exec.FdHintRead state.GetRegistersRef()[5] = addr state.GetRegistersRef()[6] = count - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) step := state.GetStep() expected := testutil.NewExpectedState(state) @@ -220,7 +219,7 @@ func FuzzStateHintRead(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) @@ -233,7 +232,7 @@ func FuzzStatePreimageRead(f *testing.F) { t.Run(v.Name, func(t *testing.T) { effAddr := addr & arch.AddressMask pc = pc & arch.AddressMask - preexistingMemoryVal := [4]byte{0xFF, 0xFF, 0xFF, 0xFF} + preexistingMemoryVal := ^arch.Word(0) preimageValue := []byte("hello world") preimageData := testutil.AddPreimageLengthPrefix(preimageValue) if preimageOffset >= Word(len(preimageData)) || pc == effAddr { @@ -249,12 +248,12 @@ func FuzzStatePreimageRead(f *testing.F) { state.GetRegistersRef()[4] = exec.FdPreimageRead state.GetRegistersRef()[5] = addr state.GetRegistersRef()[6] = count - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) - state.GetMemory().SetUint32(effAddr, binary.BigEndian.Uint32(preexistingMemoryVal[:])) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) + state.GetMemory().SetWord(effAddr, preexistingMemoryVal) step := state.GetStep() alignment := addr & arch.ExtMask - writeLen := 4 - alignment + writeLen := arch.WordSizeBytes - alignment if count < writeLen { writeLen = count } @@ -273,9 +272,10 @@ func FuzzStatePreimageRead(f *testing.F) { expected.PreimageOffset += writeLen if writeLen > 0 { // Expect a memory write - expectedMemory := preexistingMemoryVal + var expectedMemory []byte + expectedMemory = arch.ByteOrderWord.AppendWord(expectedMemory, preexistingMemoryVal) copy(expectedMemory[alignment:], preimageData[preimageOffset:preimageOffset+writeLen]) - expected.ExpectMemoryWrite(effAddr, binary.BigEndian.Uint32(expectedMemory[:])) + expected.ExpectMemoryWriteWord(effAddr, arch.ByteOrderWord.Word(expectedMemory[:])) } stepWitness, err := goVm.Step(true) @@ -283,7 +283,7 @@ func FuzzStatePreimageRead(f *testing.F) { require.True(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) @@ -331,7 +331,7 @@ func FuzzStateHintWrite(f *testing.F) { step := state.GetStep() err := state.GetMemory().SetMemoryRange(addr, bytes.NewReader(hintData[int(lastHintLen):])) require.NoError(t, err) - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) // Set up expectations expected := testutil.NewExpectedState(state) @@ -365,7 +365,7 @@ func FuzzStateHintWrite(f *testing.F) { // Validate require.Equal(t, expectedHints, oracle.Hints()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) @@ -382,7 +382,7 @@ func FuzzStatePreimageWrite(f *testing.F) { addr += 8 } effAddr := addr & arch.AddressMask - preexistingMemoryVal := [4]byte{0x12, 0x34, 0x56, 0x78} + preexistingMemoryVal := [8]byte{0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21} preimageData := []byte("hello world") preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey() oracle := testutil.StaticOracle(t, preimageData) @@ -394,13 +394,13 @@ func FuzzStatePreimageWrite(f *testing.F) { state.GetRegistersRef()[4] = exec.FdPreimageWrite state.GetRegistersRef()[5] = addr state.GetRegistersRef()[6] = count - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) - state.GetMemory().SetUint32(effAddr, binary.BigEndian.Uint32(preexistingMemoryVal[:])) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) + state.GetMemory().SetWord(effAddr, arch.ByteOrderWord.Word(preexistingMemoryVal[:])) step := state.GetStep() expectBytesWritten := count alignment := addr & arch.ExtMask - sz := 4 - alignment + sz := arch.WordSizeBytes - alignment if sz < expectBytesWritten { expectBytesWritten = sz } @@ -425,7 +425,7 @@ func FuzzStatePreimageWrite(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } }) diff --git a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go index d898391ba4d9b..49f45a4a5f2c5 100644 --- a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go @@ -27,7 +27,7 @@ func FuzzStateSyscallCloneMT(f *testing.F) { // Setup state.NextThreadId = nextThreadId - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) state.GetRegistersRef()[2] = arch.SysClone state.GetRegistersRef()[4] = exec.ValidCloneFlags state.GetRegistersRef()[5] = stackPtr @@ -62,6 +62,6 @@ func FuzzStateSyscallCloneMT(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), v.Contracts, nil) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), v.Contracts) }) } diff --git a/cannon/mipsevm/tests/fuzz_evm_singlethreaded_test.go b/cannon/mipsevm/tests/fuzz_evm_singlethreaded_test.go index 7d2c8ff6f28a8..375e1fec1832f 100644 --- a/cannon/mipsevm/tests/fuzz_evm_singlethreaded_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_singlethreaded_test.go @@ -1,10 +1,12 @@ +//go:build !cannon64 +// +build !cannon64 + package tests import ( "os" "testing" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" @@ -17,7 +19,7 @@ func FuzzStateSyscallCloneST(f *testing.F) { goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed)) state := goVm.GetState() state.GetRegistersRef()[2] = arch.SysClone - state.GetMemory().SetUint32(state.GetPC(), syscallInsn) + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) step := state.GetStep() expected := testutil.NewExpectedState(state) @@ -32,11 +34,6 @@ func FuzzStateSyscallCloneST(f *testing.F) { require.False(t, stepWitness.HasPreimage()) expected.Validate(t, state) - - evm := testutil.NewMIPSEVM(v.Contracts) - evmPost := evm.Step(t, stepWitness, step, v.StateHashFn) - goPost, _ := goVm.GetState().EncodeWitness() - require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), - "mipsevm produced different state than EVM") + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) }) } diff --git a/cannon/mipsevm/tests/helpers.go b/cannon/mipsevm/tests/helpers.go index 3cbbcccefea79..c4f857f34b832 100644 --- a/cannon/mipsevm/tests/helpers.go +++ b/cannon/mipsevm/tests/helpers.go @@ -129,3 +129,20 @@ func GetMipsVersionTestCases(t require.TestingT) []VersionedVMTestCase { } } } + +type threadProofTestcase struct { + Name string + Proof []byte +} + +func GenerateEmptyThreadProofVariations(t require.TestingT) []threadProofTestcase { + defaultThreadProof := multiThreadedProofGenerator(t, multithreaded.CreateEmptyState()) + zeroBytesThreadProof := make([]byte, multithreaded.THREAD_WITNESS_SIZE) + copy(zeroBytesThreadProof[multithreaded.SERIALIZED_THREAD_SIZE:], defaultThreadProof[multithreaded.SERIALIZED_THREAD_SIZE:]) + nilBytesThreadProof := defaultThreadProof[multithreaded.SERIALIZED_THREAD_SIZE:] + return []threadProofTestcase{ + {Name: "default thread proof", Proof: defaultThreadProof}, + {Name: "zeroed thread bytes proof", Proof: zeroBytesThreadProof}, + {Name: "nil thread bytes proof", Proof: nilBytesThreadProof}, + } +} diff --git a/cannon/mipsevm/tests/testfuncs_test.go b/cannon/mipsevm/tests/testfuncs_test.go new file mode 100644 index 0000000000000..0087ab445d737 --- /dev/null +++ b/cannon/mipsevm/tests/testfuncs_test.go @@ -0,0 +1,502 @@ +package tests + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" + mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" + preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum/go-ethereum/crypto" +) + +type operatorTestCase struct { + name string + isImm bool + rs Word + rt Word + imm uint16 + funct uint32 + opcode uint32 + expectRes Word +} + +func testOperators(t *testing.T, cases []operatorTestCase, mips32Insn bool) { + versions := GetMipsVersionTestCases(t) + for _, v := range versions { + for i, tt := range cases { + // sign extend inputs for 64-bit compatibility + if mips32Insn { + tt.rs = randomizeUpperWord(signExtend64(tt.rs)) + tt.rt = randomizeUpperWord(signExtend64(tt.rt)) + tt.expectRes = signExtend64(tt.expectRes) + } + + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + validator := testutil.NewEvmValidator(t, v.StateHashFn, v.Contracts) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + state := goVm.GetState() + var insn uint32 + var baseReg uint32 = 17 + var rtReg uint32 + var rdReg uint32 + if tt.isImm { + rtReg = 8 + insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | uint32(tt.imm) + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[baseReg] = tt.rs + } else { + rtReg = 18 + rdReg = 8 + insn = baseReg<<21 | rtReg<<16 | rdReg<<11 | tt.funct + state.GetRegistersRef()[baseReg] = tt.rs + state.GetRegistersRef()[rtReg] = tt.rt + } + testutil.StoreInstruction(state.GetMemory(), 0, insn) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.Step += 1 + expected.PC = 4 + expected.NextPC = 8 + if tt.isImm { + expected.Registers[rtReg] = tt.expectRes + } else { + expected.Registers[rdReg] = tt.expectRes + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + validator.ValidateEVM(t, stepWitness, step, goVm) + }) + } + } +} + +type mulDivTestCase struct { + name string + rs Word + rt Word + funct uint32 + opcode uint32 + expectHi Word + expectLo Word + expectRes Word + rdReg uint32 + panicMsg string + revertMsg string +} + +func testMulDiv(t *testing.T, cases []mulDivTestCase, mips32Insn bool) { + versions := GetMipsVersionTestCases(t) + for _, v := range versions { + for i, tt := range cases { + if mips32Insn { + tt.rs = randomizeUpperWord(signExtend64(tt.rs)) + tt.rt = randomizeUpperWord(signExtend64(tt.rt)) + tt.expectHi = signExtend64(tt.expectHi) + tt.expectLo = signExtend64(tt.expectLo) + tt.expectRes = signExtend64(tt.expectRes) + } + + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + state := goVm.GetState() + var insn uint32 + baseReg := uint32(0x9) + rtReg := uint32(0xa) + + insn = tt.opcode<<26 | baseReg<<21 | rtReg<<16 | tt.rdReg<<11 | tt.funct + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[baseReg] = tt.rs + testutil.StoreInstruction(state.GetMemory(), 0, insn) + + if tt.panicMsg != "" { + proofData := v.ProofGenerator(t, goVm.GetState()) + require.PanicsWithValue(t, tt.panicMsg, func() { + _, _ = goVm.Step( + false) + }) + testutil.AssertEVMReverts(t, state, v.Contracts, nil, proofData, testutil.CreateErrorStringMatcher(tt.revertMsg)) + return + } + + step := state.GetStep() + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + if tt.expectRes != 0 { + expected.Registers[tt.rdReg] = tt.expectRes + } else { + expected.HI = tt.expectHi + expected.LO = tt.expectLo + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } + } +} + +type loadStoreTestCase struct { + name string + rt Word + base Word + imm uint32 + opcode uint32 + memVal Word + expectMemVal Word + expectRes Word +} + +func testLoadStore(t *testing.T, cases []loadStoreTestCase) { + baseReg := uint32(9) + rtReg := uint32(8) + + v := GetMultiThreadedTestCase(t) + for i, tt := range cases { + testName := fmt.Sprintf("%v %v", v.Name, tt.name) + t.Run(testName, func(t *testing.T) { + addr := tt.base + Word(tt.imm) + effAddr := arch.AddressMask & addr + + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0)) + state := goVm.GetState() + + insn := tt.opcode<<26 | baseReg<<21 | rtReg<<16 | uint32(tt.imm) + state.GetRegistersRef()[rtReg] = tt.rt + state.GetRegistersRef()[baseReg] = tt.base + + testutil.StoreInstruction(state.GetMemory(), 0, insn) + state.GetMemory().SetWord(effAddr, tt.memVal) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + if tt.expectMemVal != 0 { + expected.ExpectMemoryWriteWord(effAddr, tt.expectMemVal) + } else { + expected.Registers[rtReg] = tt.expectRes + } + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } +} + +type branchTestCase struct { + name string + pc Word + expectNextPC Word + opcode uint32 + regimm uint32 + expectLink bool + rs arch.SignedInteger + offset uint16 +} + +func testBranch(t *testing.T, cases []branchTestCase) { + versions := GetMipsVersionTestCases(t) + for _, v := range versions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(tt.pc)) + state := goVm.GetState() + const rsReg = 8 // t0 + insn := tt.opcode<<26 | rsReg<<21 | tt.regimm<<16 | uint32(tt.offset) + testutil.StoreInstruction(state.GetMemory(), tt.pc, insn) + state.GetRegistersRef()[rsReg] = Word(tt.rs) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.Step += 1 + expected.PC = state.GetCpu().NextPC + expected.NextPC = tt.expectNextPC + if tt.expectLink { + expected.Registers[31] = state.GetPC() + 8 + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } + } +} + +type testMTStoreOpsClearMemReservationTestCase struct { + // name is the test name + name string + // opcode is the instruction opcode + opcode uint32 + // offset is the immediate offset encoded in the instruction + offset uint32 + // base is the base/rs register prestate + base Word + // effAddr is the address used to set the prestate preMem value. It is also used as the base LLAddress that can be adjusted reservation assertions + effAddr Word + // premem is the prestate value of the word located at effrAddr + preMem Word + // postMem is the expected post-state value of the word located at effAddr + postMem Word +} + +func testMTStoreOpsClearMemReservation(t *testing.T, cases []testMTStoreOpsClearMemReservationTestCase) { + llVariations := []struct { + name string + llReservationStatus multithreaded.LLReservationStatus + matchThreadId bool + effAddrOffset Word + shouldClearReservation bool + }{ + {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true}, + {name: "matching reservation, diff thread, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, shouldClearReservation: true}, + {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + {name: "mismatched reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false}, + {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true}, + {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + } + + rt := Word(0x12_34_56_78) + //rt := Word(0x12_34_56_78_12_34_56_78) + baseReg := uint32(5) + rtReg := uint32(6) + for i, c := range cases { + for _, v := range llVariations { + tName := fmt.Sprintf("%v (%v)", c.name, v.name) + t.Run(tName, func(t *testing.T) { + t.Parallel() + insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) + goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x08)) + step := state.GetStep() + + // Define LL-related params + llAddress := c.effAddr + v.effAddrOffset + llOwnerThread := state.GetCurrentThread().ThreadId + if !v.matchThreadId { + llOwnerThread += 1 + } + + // Setup state + state.GetRegistersRef()[rtReg] = rt + state.GetRegistersRef()[baseReg] = c.base + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetMemory().SetWord(c.effAddr, c.preMem) + state.LLReservationStatus = v.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ExpectMemoryWordWrite(c.effAddr, c.postMem) + if v.shouldClearReservation { + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } + } +} + +type testMTSysReadPreimageTestCase struct { + name string + addr Word + count Word + writeLen Word + preimageOffset Word + prestateMem Word + postateMem Word + shouldPanic bool +} + +func testMTSysReadPreimage(t *testing.T, preimageValue []byte, cases []testMTSysReadPreimageTestCase) { + llVariations := []struct { + name string + llReservationStatus multithreaded.LLReservationStatus + matchThreadId bool + effAddrOffset Word + shouldClearReservation bool + }{ + {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true}, + {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true}, + {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false}, + {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true}, + {name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false}, + } + + for i, c := range cases { + for _, v := range llVariations { + tName := fmt.Sprintf("%v (%v)", c.name, v.name) + t.Run(tName, func(t *testing.T) { + t.Parallel() + effAddr := arch.AddressMask & c.addr + preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey() + oracle := testutil.StaticOracle(t, preimageValue) + goVm, state, contracts := setup(t, i, oracle) + step := state.GetStep() + + // Define LL-related params + llAddress := effAddr + v.effAddrOffset + llOwnerThread := state.GetCurrentThread().ThreadId + if !v.matchThreadId { + llOwnerThread += 1 + } + + // Set up state + state.PreimageKey = preimageKey + state.PreimageOffset = c.preimageOffset + state.GetRegistersRef()[2] = arch.SysRead + state.GetRegistersRef()[4] = exec.FdPreimageRead + state.GetRegistersRef()[5] = c.addr + state.GetRegistersRef()[6] = c.count + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syscallInsn) + state.LLReservationStatus = v.llReservationStatus + state.LLAddress = llAddress + state.LLOwnerThread = llOwnerThread + state.GetMemory().SetWord(effAddr, c.prestateMem) + + // Setup expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = c.writeLen + expected.ActiveThread().Registers[7] = 0 // no error + expected.PreimageOffset += c.writeLen + expected.ExpectMemoryWordWrite(effAddr, c.postateMem) + if v.shouldClearReservation { + expected.LLReservationStatus = multithreaded.LLStatusNone + expected.LLAddress = 0 + expected.LLOwnerThread = 0 + } + + if c.shouldPanic { + require.Panics(t, func() { _, _ = goVm.Step(true) }) + testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, contracts) + } else { + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + } + }) + } + } +} + +func testNoopSyscall(t *testing.T, syscalls map[string]uint32) { + for noopName, noopVal := range syscalls { + t.Run(noopName, func(t *testing.T) { + t.Parallel() + goVm, state, contracts := setup(t, int(noopVal), nil) + + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = Word(noopVal) // Set syscall number + step := state.Step + + // Set up post-state expectations + expected := mttestutil.NewExpectedMTState(state) + expected.ExpectStep() + expected.ActiveThread().Registers[2] = 0 + expected.ActiveThread().Registers[7] = 0 + + // State transition + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + + // Validate post-state + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) + }) + } +} + +func testUnsupportedSyscall(t *testing.T, unsupportedSyscalls []uint32) { + for i, syscallNum := range unsupportedSyscalls { + testName := fmt.Sprintf("Unsupported syscallNum %v", syscallNum) + i := i + syscallNum := syscallNum + t.Run(testName, func(t *testing.T) { + t.Parallel() + goVm, state, contracts := setup(t, i*3434, nil) + // Setup basic getThreadId syscall instruction + testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) + state.GetRegistersRef()[2] = Word(syscallNum) + proofData := multiThreadedProofGenerator(t, state) + // Set up post-state expectations + require.Panics(t, func() { _, _ = goVm.Step(true) }) + + errorMessage := "unimplemented syscall" + testutil.AssertEVMReverts(t, state, contracts, nil, proofData, testutil.CreateErrorStringMatcher(errorMessage)) + }) + } +} + +// signExtend64 is used to sign-extend 32-bit words for 64-bit compatibility +func signExtend64(w Word) Word { + if arch.IsMips32 { + return w + } else { + return exec.SignExtend(w, 32) + } +} + +const seed = 0xdead + +var rand = testutil.NewRandHelper(seed) + +// randomizeUpperWord is used to assert that 32-bit operations use the lower word only +func randomizeUpperWord(w Word) Word { + if arch.IsMips32 { + return w + } else { + if w>>32 == 0x0 { // nolint:staticcheck + rnd := rand.Uint32() + upper := uint64(rnd) << 32 + return Word(upper | uint64(uint32(w))) + } else { + return w + } + } +} diff --git a/cannon/mipsevm/testutil/arch.go b/cannon/mipsevm/testutil/arch.go new file mode 100644 index 0000000000000..b4eb50b7e3f17 --- /dev/null +++ b/cannon/mipsevm/testutil/arch.go @@ -0,0 +1,56 @@ +// This file contains utils for setting up forward-compatible tests for 32- and 64-bit MIPS VMs + +package testutil + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" +) + +type Word = arch.Word + +// SetMemoryUint64 sets 8 bytes of memory (1 or 2 Words depending on architecture) and enforces the use of addresses +// that are compatible across 32- and 64-bit architectures +func SetMemoryUint64(t require.TestingT, mem *memory.Memory, addr Word, value uint64) { + // We are setting 8 bytes of data, so mask addr to align with 8-byte boundaries in memory + addrMask := ^Word(0) & ^Word(7) + targetAddr := addr & addrMask + + data := Uint64ToBytes(value) + err := mem.SetMemoryRange(targetAddr, bytes.NewReader(data)) + require.NoError(t, err) + + // Sanity check + if addr&0x04 != 0x04 { + // In order to write tests that run seamlessly across both 32- and 64-bit architectures, + // we need to use a memory address that is a multiple of 4, but not a multiple of 8. + // This allows us to expect a consistent value when getting a 32-bit memory value at the given address. + // For example, if memory contains [0x00: 0x1111_2222, 0x04: 0x3333_4444]: + // - the 64-bit MIPS VM will get effAddr 0x00, pulling the rightmost (lower-order) 32-bit value + // - the 32-bit MIPS VM will get effAddr 0x04, pulling the same 32-bit value + t.Errorf("Invalid address used to set uint64 memory value: %016x", addr) + t.FailNow() + } + // Give the above addr check, memory access should return the same value across architectures + effAddr := addr & arch.AddressMask + actual := mem.GetWord(effAddr) + require.Equal(t, Word(value), actual) +} + +// ToSignedInteger converts the unsigend Word to a SignedInteger. +// Useful for avoiding Go compiler warnings for literals that don't fit in a signed type +func ToSignedInteger(x Word) arch.SignedInteger { + return arch.SignedInteger(x) +} + +// Cannon32OnlyTest skips the test if it's not a cannon64 build +func Cannon32OnlyTest(t testing.TB, msg string, args ...any) { + if !arch.IsMips32 { + t.Skipf(msg, args...) + } +} diff --git a/cannon/mipsevm/testutil/elf.go b/cannon/mipsevm/testutil/elf.go index b5b63cdeb1d39..3e1896963cda4 100644 --- a/cannon/mipsevm/testutil/elf.go +++ b/cannon/mipsevm/testutil/elf.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/cannon/mipsevm" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/program" ) @@ -26,3 +27,12 @@ func LoadELFProgram[T mipsevm.FPVMState](t require.TestingT, name string, initSt require.NoError(t, program.PatchStack(state), "add initial stack") return state, meta } + +// ProgramPath returns the appropriate ELF test program for the current architecture +func ProgramPath(programName string) string { + basename := programName + ".elf" + if !arch.IsMips32 { + basename = programName + ".64.elf" + } + return "../../testdata/example/bin/" + basename +} diff --git a/cannon/mipsevm/testutil/evm.go b/cannon/mipsevm/testutil/evm.go index 6d424ae62493e..6a4832c2953df 100644 --- a/cannon/mipsevm/testutil/evm.go +++ b/cannon/mipsevm/testutil/evm.go @@ -7,6 +7,7 @@ import ( "math/big" "os" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/op-chain-ops/srcmap" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/eth/tracers/logger" @@ -66,7 +67,11 @@ func loadArtifacts(version MipsVersion) (*Artifacts, error) { case MipsSingleThreaded: mips, err = artifactFS.ReadArtifact("MIPS.sol", "MIPS") case MipsMultithreaded: - mips, err = artifactFS.ReadArtifact("MIPS2.sol", "MIPS2") + if arch.IsMips32 { + mips, err = artifactFS.ReadArtifact("MIPS2.sol", "MIPS2") + } else { + mips, err = artifactFS.ReadArtifact("MIPS64.sol", "MIPS64") + } default: return nil, fmt.Errorf("Unknown MipsVersion supplied: %v", version) } @@ -167,7 +172,11 @@ func SourceMapTracer(t require.TestingT, version MipsVersion, mips *foundry.Arti case MipsSingleThreaded: mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS") case MipsMultithreaded: - mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS2") + if arch.IsMips32 { + mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS2") + } else { + mipsSrcMap, err = srcFS.SourceMap(mips, "MIPS64") + } default: require.Fail(t, "invalid mips version") } diff --git a/cannon/mipsevm/testutil/memory.go b/cannon/mipsevm/testutil/memory.go new file mode 100644 index 0000000000000..ca41453aaf62b --- /dev/null +++ b/cannon/mipsevm/testutil/memory.go @@ -0,0 +1,38 @@ +package testutil + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" +) + +func Uint32ToBytes(val uint32) []byte { + data := make([]byte, 4) + binary.BigEndian.PutUint32(data, val) + + return data +} + +func Uint64ToBytes(val uint64) []byte { + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, val) + + return data +} + +// StoreInstruction writes a 4-byte instruction to memory +func StoreInstruction(mem *memory.Memory, pc Word, insn uint32) { + if pc&0x3 != 0 { + panic(fmt.Errorf("unaligned memory access: %x", pc)) + } + exec.StoreSubWord(mem, pc, 4, Word(insn), new(exec.NoopMemoryTracker)) +} + +func GetInstruction(mem *memory.Memory, pc Word) uint32 { + if pc&0x3 != 0 { + panic(fmt.Errorf("unaligned memory access: %x", pc)) + } + return uint32(exec.LoadSubWord(mem, pc, 4, false, new(exec.NoopMemoryTracker))) +} diff --git a/cannon/mipsevm/testutil/mips.go b/cannon/mipsevm/testutil/mips.go index b2c0fc2f58062..0d5322c6fb6ef 100644 --- a/cannon/mipsevm/testutil/mips.go +++ b/cannon/mipsevm/testutil/mips.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/cannon/mipsevm" @@ -22,6 +23,9 @@ import ( preimage "github.com/ethereum-optimism/optimism/op-preimage" ) +// maxStepGas should be less than the L1 gas limit +const maxStepGas = 20_000_000 + type MIPSEVM struct { sender vm.AccountRef startingGas uint64 @@ -36,11 +40,35 @@ type MIPSEVM struct { lastPreimageOracleInput []byte } -func NewMIPSEVM(contracts *ContractMetadata) *MIPSEVM { +func newMIPSEVM(contracts *ContractMetadata, opts ...evmOption) *MIPSEVM { env, evmState := NewEVMEnv(contracts) sender := vm.AccountRef{0x13, 0x37} - startingGas := uint64(30_000_000) - return &MIPSEVM{sender, startingGas, env, evmState, contracts.Addresses, nil, contracts.Artifacts, math.MaxUint64, nil, nil} + startingGas := uint64(maxStepGas) + evm := &MIPSEVM{sender, startingGas, env, evmState, contracts.Addresses, nil, contracts.Artifacts, math.MaxUint64, nil, nil} + for _, opt := range opts { + opt(evm) + } + return evm +} + +type evmOption func(c *MIPSEVM) + +func WithSourceMapTracer(t *testing.T, ver MipsVersion) evmOption { + return func(evm *MIPSEVM) { + evm.SetSourceMapTracer(t, ver) + } +} + +func WithTracingHooks(tracer *tracing.Hooks) evmOption { + return func(evm *MIPSEVM) { + evm.SetTracer(tracer) + } +} + +func WithLocalOracle(oracle mipsevm.PreimageOracle) evmOption { + return func(evm *MIPSEVM) { + evm.SetLocalOracle(oracle) + } } func (m *MIPSEVM) SetTracer(tracer *tracing.Hooks) { @@ -170,27 +198,68 @@ func LogStepFailureAtCleanup(t *testing.T, mipsEvm *MIPSEVM) { }) } -// ValidateEVM runs a single evm step and validates against an FPVM poststate -func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, tracer *tracing.Hooks) { - evm := NewMIPSEVM(contracts) - evm.SetTracer(tracer) +type EvmValidator struct { + evm *MIPSEVM + hashFn mipsevm.HashFn +} + +// NewEvmValidator creates a validator that can be run repeatedly across multiple steps +func NewEvmValidator(t *testing.T, hashFn mipsevm.HashFn, contracts *ContractMetadata, opts ...evmOption) *EvmValidator { + evm := newMIPSEVM(contracts, opts...) LogStepFailureAtCleanup(t, evm) - evmPost := evm.Step(t, stepWitness, step, hashFn) + return &EvmValidator{ + evm: evm, + hashFn: hashFn, + } +} + +func (v *EvmValidator) ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM) { + evmPost := v.evm.Step(t, stepWitness, step, v.hashFn) goPost, _ := goVm.GetState().EncodeWitness() require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(), "mipsevm produced different state than EVM") } +// ValidateEVM runs a single evm step and validates against an FPVM poststate +func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, opts ...evmOption) { + validator := NewEvmValidator(t, hashFn, contracts) + validator.ValidateEVM(t, stepWitness, step, goVm) +} + +type ErrMatcher func(*testing.T, []byte) + +func CreateNoopErrorMatcher() ErrMatcher { + return func(t *testing.T, ret []byte) {} +} + +// CreateErrorStringMatcher matches an Error(string) +func CreateErrorStringMatcher(expect string) ErrMatcher { + return func(t *testing.T, ret []byte) { + require.Greaterf(t, len(ret), 4, "Return data length should be greater than 4 bytes: %x", ret) + unpacked, decodeErr := abi.UnpackRevert(ret) + require.NoError(t, decodeErr, "Failed to unpack revert reason") + require.Contains(t, unpacked, expect, "Revert reason mismatch") + } +} + +// CreateCustomErrorMatcher matches a custom error given the error signature +func CreateCustomErrorMatcher(sig string) ErrMatcher { + return func(t *testing.T, ret []byte) { + expect := crypto.Keccak256([]byte(sig))[:4] + require.EqualValuesf(t, expect, ret, "return value is %x", ret) + } +} + // AssertEVMReverts runs a single evm step from an FPVM prestate and asserts that the VM panics -func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *ContractMetadata, tracer *tracing.Hooks, ProofData []byte, expectedReason string) { +func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *ContractMetadata, tracer *tracing.Hooks, ProofData []byte, matcher ErrMatcher) { encodedWitness, _ := state.EncodeWitness() stepWitness := &mipsevm.StepWitness{ State: encodedWitness, ProofData: ProofData, } input := EncodeStepInput(t, stepWitness, mipsevm.LocalContext{}, contracts.Artifacts.MIPS) - startingGas := uint64(30_000_000) + startingGas := uint64(maxStepGas) env, evmState := NewEVMEnv(contracts) env.Config.Tracer = tracer @@ -198,18 +267,14 @@ func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *Contract ret, _, err := env.Call(vm.AccountRef(sender), contracts.Addresses.MIPS, input, startingGas, common.U2560) require.EqualValues(t, err, vm.ErrExecutionReverted) - require.Greater(t, len(ret), 4, "Return data length should be greater than 4 bytes") - unpacked, decodeErr := abi.UnpackRevert(ret) - require.NoError(t, decodeErr, "Failed to unpack revert reason") - require.Equal(t, expectedReason, unpacked, "Revert reason mismatch") + matcher(t, ret) logs := evmState.Logs() require.Equal(t, 0, len(logs)) } -func AssertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word, contracts *ContractMetadata, tracer *tracing.Hooks) { - evm := NewMIPSEVM(contracts) - evm.SetTracer(tracer) +func AssertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word, contracts *ContractMetadata, opts ...evmOption) { + evm := newMIPSEVM(contracts, opts...) LogStepFailureAtCleanup(t, evm) evm.assertPreimageOracleReverts(t, preimageKey, preimageValue, preimageOffset) diff --git a/cannon/mipsevm/testutil/oracle.go b/cannon/mipsevm/testutil/oracle.go index 64b0f31a897c9..b5c4f7dce9151 100644 --- a/cannon/mipsevm/testutil/oracle.go +++ b/cannon/mipsevm/testutil/oracle.go @@ -67,7 +67,7 @@ func StaticPrecompileOracle(t *testing.T, precompile common.Address, requiredGas } func ClaimTestOracle(t *testing.T) (po mipsevm.PreimageOracle, stdOut string, stdErr string) { - s := uint64(1000) + s := uint64(0x00FFFFFF_00001000) a := uint64(3) b := uint64(4) diff --git a/cannon/mipsevm/testutil/state.go b/cannon/mipsevm/testutil/state.go index 0c247a7fb26de..42e4b8851ac31 100644 --- a/cannon/mipsevm/testutil/state.go +++ b/cannon/mipsevm/testutil/state.go @@ -68,6 +68,18 @@ func WithPCAndNextPC(pc arch.Word) StateOption { } } +func WithHI(hi arch.Word) StateOption { + return func(state StateMutator) { + state.SetHI(hi) + } +} + +func WithLO(lo arch.Word) StateOption { + return func(state StateMutator) { + state.SetLO(lo) + } +} + func WithHeap(addr arch.Word) StateOption { return func(state StateMutator) { state.SetHeap(addr) @@ -107,7 +119,7 @@ func WithRandomization(seed int64) StateOption { func AlignPC(pc arch.Word) arch.Word { // Memory-align random pc and leave room for nextPC pc = pc & arch.AddressMask // Align address - if pc >= arch.AddressMask && arch.IsMips32 { + if pc >= arch.AddressMask { // Leave room to set and then increment nextPC pc = arch.AddressMask - 8 } @@ -165,11 +177,6 @@ func (e *ExpectedState) ExpectStep() { e.NextPC += 4 } -func (e *ExpectedState) ExpectMemoryWrite(addr arch.Word, val uint32) { - e.expectedMemory.SetUint32(addr, val) - e.MemoryRoot = e.expectedMemory.MerkleRoot() -} - func (e *ExpectedState) ExpectMemoryWriteWord(addr arch.Word, val arch.Word) { e.expectedMemory.SetWord(addr, val) e.MemoryRoot = e.expectedMemory.MerkleRoot() diff --git a/cannon/mipsevm/testutil/vmtests.go b/cannon/mipsevm/testutil/vmtests.go index 382c608333998..333720a4ec025 100644 --- a/cannon/mipsevm/testutil/vmtests.go +++ b/cannon/mipsevm/testutil/vmtests.go @@ -16,10 +16,14 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/program" ) -type VMFactory[T mipsevm.FPVMState] func(state T, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger) mipsevm.FPVM +type VMFactory[T mipsevm.FPVMState] func(state T, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta *program.Metadata) mipsevm.FPVM type StateFactory[T mipsevm.FPVMState] func() T func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFactory[T], vmFactory VMFactory[T], excludedTests ...string) { + if !arch.IsMips32 { + // TODO: guard these tests by the cannon32 build tag + t.Skip("Open MIPS tests are not appropriate for cannon64") + } testFiles, err := os.ReadDir("../tests/open_mips_tests/test/bin") require.NoError(t, err) @@ -52,7 +56,7 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa // set the return address ($ra) to jump into when test completes state.GetRegistersRef()[31] = EndAddr - us := vmFactory(state, oracle, os.Stdout, os.Stderr, CreateLogger()) + us := vmFactory(state, oracle, os.Stdout, os.Stderr, CreateLogger(), nil) // Catch panics and check if they are expected defer func() { @@ -94,12 +98,13 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa } func RunVMTest_Hello[T mipsevm.FPVMState](t *testing.T, initState program.CreateInitialFPVMState[T], vmFactory VMFactory[T], doPatchGo bool) { - state, _ := LoadELFProgram(t, "../../testdata/example/bin/hello.elf", initState, doPatchGo) + state, meta := LoadELFProgram(t, ProgramPath("hello"), initState, doPatchGo) var stdOutBuf, stdErrBuf bytes.Buffer - us := vmFactory(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), CreateLogger()) + us := vmFactory(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), CreateLogger(), meta) - for i := 0; i < 400_000; i++ { + maxSteps := 430_000 + for i := 0; i < maxSteps; i++ { if us.GetState().GetExited() { break } @@ -107,7 +112,7 @@ func RunVMTest_Hello[T mipsevm.FPVMState](t *testing.T, initState program.Create require.NoError(t, err) } - require.True(t, state.GetExited(), "must complete program") + require.Truef(t, state.GetExited(), "must complete program. reached %d of max %d steps", state.GetStep(), maxSteps) require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0") require.Equal(t, "hello world!\n", stdOutBuf.String(), "stdout says hello") @@ -115,12 +120,12 @@ func RunVMTest_Hello[T mipsevm.FPVMState](t *testing.T, initState program.Create } func RunVMTest_Claim[T mipsevm.FPVMState](t *testing.T, initState program.CreateInitialFPVMState[T], vmFactory VMFactory[T], doPatchGo bool) { - state, _ := LoadELFProgram(t, "../../testdata/example/bin/claim.elf", initState, doPatchGo) + state, meta := LoadELFProgram(t, ProgramPath("claim"), initState, doPatchGo) oracle, expectedStdOut, expectedStdErr := ClaimTestOracle(t) var stdOutBuf, stdErrBuf bytes.Buffer - us := vmFactory(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), CreateLogger()) + us := vmFactory(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), CreateLogger(), meta) for i := 0; i < 2000_000; i++ { if us.GetState().GetExited() { diff --git a/cannon/mipsevm/versions/detect_test.go b/cannon/mipsevm/versions/detect_test.go index be849269fff9b..bd1acd115eafe 100644 --- a/cannon/mipsevm/versions/detect_test.go +++ b/cannon/mipsevm/versions/detect_test.go @@ -7,10 +7,12 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" "github.com/ethereum-optimism/optimism/op-service/ioutil" - "github.com/stretchr/testify/require" ) const statesPath = "testdata/states" @@ -18,7 +20,7 @@ const statesPath = "testdata/states" //go:embed testdata/states var historicStates embed.FS -func TestDetectVersion(t *testing.T) { +func TestDetectVersion_fromFile(t *testing.T) { testDetection := func(t *testing.T, version StateVersion, ext string) { filename := strconv.Itoa(int(version)) + ext dir := t.TempDir() @@ -34,9 +36,6 @@ func TestDetectVersion(t *testing.T) { // Iterate all known versions to ensure we have a test case to detect every state version for _, version := range StateVersionTypes { version := version - if version == VersionMultiThreaded64 { - t.Skip("TODO(#12205)") - } t.Run(version.String(), func(t *testing.T) { testDetection(t, version, ".bin.gz") }) @@ -47,28 +46,38 @@ func TestDetectVersion(t *testing.T) { }) } } +} - // Additionally, check that the latest supported versions write new states in a way that is detected correctly - t.Run("SingleThreadedBinary", func(t *testing.T) { - state, err := NewFromState(singlethreaded.CreateEmptyState()) - require.NoError(t, err) - path := writeToFile(t, "state.bin.gz", state) - version, err := DetectVersion(path) - require.NoError(t, err) - require.Equal(t, VersionSingleThreaded2, version) - }) +// Check that the latest supported versions write new states in a way that is detected correctly +func TestDetectVersion_singleThreadedBinary(t *testing.T) { + targetVersion := VersionSingleThreaded2 + if !arch.IsMips32 { + t.Skip("Single-threaded states are not supported for 64-bit VMs") + } - t.Run("MultiThreadedBinary", func(t *testing.T) { - state, err := NewFromState(multithreaded.CreateEmptyState()) - require.NoError(t, err) - path := writeToFile(t, "state.bin.gz", state) - version, err := DetectVersion(path) - require.NoError(t, err) - require.Equal(t, VersionMultiThreaded, version) - }) + state, err := NewFromState(singlethreaded.CreateEmptyState()) + require.NoError(t, err) + path := writeToFile(t, "state.bin.gz", state) + version, err := DetectVersion(path) + require.NoError(t, err) + require.Equal(t, targetVersion, version) +} + +func TestDetectVersion_multiThreadedBinary(t *testing.T) { + targetVersion := VersionMultiThreaded + if !arch.IsMips32 { + targetVersion = VersionMultiThreaded64 + } + + state, err := NewFromState(multithreaded.CreateEmptyState()) + require.NoError(t, err) + path := writeToFile(t, "state.bin.gz", state) + version, err := DetectVersion(path) + require.NoError(t, err) + require.Equal(t, targetVersion, version) } -func TestDetectVersionInvalid(t *testing.T) { +func TestDetectVersion_invalid(t *testing.T) { t.Run("bad gzip", func(t *testing.T) { dir := t.TempDir() filename := "state.bin.gz" diff --git a/cannon/mipsevm/versions/state64_test.go b/cannon/mipsevm/versions/state64_test.go new file mode 100644 index 0000000000000..ddffd7e618a52 --- /dev/null +++ b/cannon/mipsevm/versions/state64_test.go @@ -0,0 +1,64 @@ +//go:build cannon64 +// +build cannon64 + +package versions + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" + "github.com/ethereum-optimism/optimism/op-service/serialize" +) + +func TestNewFromState(t *testing.T) { + t.Run("multithreaded64", func(t *testing.T) { + actual, err := NewFromState(multithreaded.CreateEmptyState()) + require.NoError(t, err) + require.IsType(t, &multithreaded.State{}, actual.FPVMState) + require.Equal(t, VersionMultiThreaded64, actual.Version) + }) +} + +func TestLoadStateFromFile(t *testing.T) { + t.Run("Multithreaded64FromBinary", func(t *testing.T) { + expected, err := NewFromState(multithreaded.CreateEmptyState()) + require.NoError(t, err) + + path := writeToFile(t, "state.bin.gz", expected) + actual, err := LoadStateFromFile(path) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) +} + +func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { + tests := []struct { + version StateVersion + createState func() mipsevm.FPVMState + }{ + {VersionMultiThreaded64, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, + } + for _, test := range tests { + test := test + t.Run(test.version.String(), func(t *testing.T) { + state, err := NewFromState(test.createState()) + require.NoError(t, err) + + dir := t.TempDir() + path := filepath.Join(dir, "test.json") + err = serialize.Write(path, state, 0o644) + require.ErrorIs(t, err, ErrJsonNotSupported) + }) + } +} + +func writeToFile(t *testing.T, filename string, data serialize.Serializable) string { + dir := t.TempDir() + path := filepath.Join(dir, filename) + require.NoError(t, serialize.Write(path, data, 0o644)) + return path +} diff --git a/cannon/mipsevm/versions/state_test.go b/cannon/mipsevm/versions/state_test.go index c9bcc7e6f165c..cf8eed639af9e 100644 --- a/cannon/mipsevm/versions/state_test.go +++ b/cannon/mipsevm/versions/state_test.go @@ -1,14 +1,18 @@ +//go:build !cannon64 +// +build !cannon64 + package versions import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded" "github.com/ethereum-optimism/optimism/op-service/serialize" - "github.com/stretchr/testify/require" ) func TestNewFromState(t *testing.T) { @@ -49,10 +53,6 @@ func TestLoadStateFromFile(t *testing.T) { }) } -func TestLoadStateFromFile64(t *testing.T) { - t.Skip("TODO(#12205): Test asserting that cannon64 fails to decode a 32-bit state") -} - func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { tests := []struct { version StateVersion diff --git a/cannon/mipsevm/versions/testdata/states/3.bin.gz b/cannon/mipsevm/versions/testdata/states/3.bin.gz new file mode 100644 index 0000000000000..b6f6728313a7d Binary files /dev/null and b/cannon/mipsevm/versions/testdata/states/3.bin.gz differ diff --git a/cannon/scripts/build-legacy-cannons.sh b/cannon/scripts/build-legacy-cannons.sh new file mode 100755 index 0000000000000..3df0a43c31eac --- /dev/null +++ b/cannon/scripts/build-legacy-cannons.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -euo pipefail +SCRIPTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +# This script builds a version of the cannon executable that includes support for both current and legacy state versions. +# Each cannon release is built separately. + +TMP_DIR=$(mktemp -d) +function cleanup() { + rm -rf "${TMP_DIR}" +} +trap cleanup EXIT +echo "Using temp dir: ${TMP_DIR}" +cd "${TMP_DIR}" + +# Need to check out a fresh copy of the monorepo so we can switch to specific tags without it also affecting the +# contents of this script (which is checked into the repo). +git clone https://github.com/ethereum-optimism/optimism --recurse-submodules + +CANNON_DIR="${SCRIPTS_DIR}/../" +EMBEDS_DIR="${CANNON_DIR}/multicannon/embeds" +LOGS_DIR="${CANNON_DIR}/temp/logs" +REPO_DIR="${TMP_DIR}/optimism" +BIN_DIR="${REPO_DIR}/cannon/multicannon/embeds" + +mkdir -p "${LOGS_DIR}" + +cd "${REPO_DIR}" + +function buildVersion() { + TAG=${1} + LOG_FILE="${LOGS_DIR}/build-$(echo "${TAG}" | cut -c 8-).txt" + echo "Building Version: ${TAG} Logs: ${LOG_FILE}" + git checkout "${TAG}" > "${LOG_FILE}" 2>&1 + git submodule update --init --recursive >> "${LOG_FILE}" 2>&1 + rm -rf "${BIN_DIR}/cannon-"* + make -C "${REPO_DIR}/cannon" cannon-embeds >> "${LOG_FILE}" 2>&1 + cp "${BIN_DIR}/cannon-"* "${EMBEDS_DIR}/" + echo "Built ${TAG} with versions:" + (cd "${BIN_DIR}" && ls cannon-*) +} + +# Build each release of cannon from earliest to latest. Releases with qualifiers (e.g. `-rc`, `-alpha` etc are skipped. +# If the same state version is supported by multiple version tags built, the build from the last tag listed will be used +# The currently checked out code is built after this list to include the currently supported state versions and +# build the final cannon executable. +VERSIONS=$(git tag --list 'cannon/v*' --sort taggerdate | grep -v -- '-') +for VERSION in ${VERSIONS} +do + buildVersion "$VERSION" +done + +cd "${CANNON_DIR}" +LOG_FILE="${LOGS_DIR}/build-current.txt" +echo "Building current version of cannon Logs: ${LOG_FILE}" +make cannon > "${LOG_FILE}" 2>&1 + +echo "All cannon versions successfully built and available in ${EMBEDS_DIR}" +"${CANNON_DIR}/bin/cannon" list diff --git a/cannon/testdata/example/Makefile b/cannon/testdata/example/Makefile index 7b3b8fdf019d2..5b0dee96eb9ad 100644 --- a/cannon/testdata/example/Makefile +++ b/cannon/testdata/example/Makefile @@ -1,7 +1,13 @@ all: elf +.PHONY: elf32 +elf32: $(patsubst %/go.mod,bin/%.elf,$(wildcard */go.mod)) + +.PHONY: elf64 +elf64: $(patsubst %/go.mod,bin/%.64.elf,$(wildcard */go.mod)) + .PHONY: elf -elf: $(patsubst %/go.mod,bin/%.elf,$(wildcard */go.mod)) +elf: elf32 elf64 .PHONY: dump dump: $(patsubst %/go.mod,bin/%.dump,$(wildcard */go.mod)) @@ -9,6 +15,9 @@ dump: $(patsubst %/go.mod,bin/%.dump,$(wildcard */go.mod)) bin: mkdir bin +bin/%.64.elf: bin + cd $(@:bin/%.64.elf=%) && GOOS=linux GOARCH=mips64 GOMIPS64=softfloat go build -o ../$@ . + # take any directory with a go mod, and build an ELF # verify output with: readelf -h bin/.elf # result is mips32, big endian, R3000 diff --git a/cannon/testdata/example/alloc/go.mod b/cannon/testdata/example/alloc/go.mod index a5dd16ce8b273..c364668acc106 100644 --- a/cannon/testdata/example/alloc/go.mod +++ b/cannon/testdata/example/alloc/go.mod @@ -1,6 +1,6 @@ module alloc -go 1.22 +go 1.22.0 toolchain go1.22.7 diff --git a/cannon/testdata/example/claim/go.mod b/cannon/testdata/example/claim/go.mod index dd76391de26ba..e3904911a172d 100644 --- a/cannon/testdata/example/claim/go.mod +++ b/cannon/testdata/example/claim/go.mod @@ -1,6 +1,6 @@ module claim -go 1.22 +go 1.22.0 toolchain go1.22.7 diff --git a/cannon/testdata/example/hello/go.mod b/cannon/testdata/example/hello/go.mod index b54bb78c6aee6..f036b5dc97fce 100644 --- a/cannon/testdata/example/hello/go.mod +++ b/cannon/testdata/example/hello/go.mod @@ -1,5 +1,5 @@ module hello -go 1.22 +go 1.22.0 -toolchain go1.22.0 +toolchain go1.22.7 diff --git a/cannon/testdata/example/mt-atomic/atomic_test_copy.go b/cannon/testdata/example/mt-atomic/atomic_test_copy.go new file mode 100644 index 0000000000000..e0cd1ebd69ff9 --- /dev/null +++ b/cannon/testdata/example/mt-atomic/atomic_test_copy.go @@ -0,0 +1,2567 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/atomic/atomic_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "fmt" + "reflect" + "runtime" + "runtime/debug" + "strings" + . "sync/atomic" + "testing" + "unsafe" + + "utils/testutil" +) + +// Tests of correct behavior, without contention. +// (Does the function work as advertised?) +// +// Test that the Add functions add correctly. +// Test that the CompareAndSwap functions actually +// do the comparison and the swap correctly. +// +// The loop over power-of-two values is meant to +// ensure that the operations apply to the full word size. +// The struct fields x.before and x.after check that the +// operations do not extend past the full word size. + +const ( + magic32 = 0xdedbeef + magic64 = 0xdeddeadbeefbeef +) + +func TestSwapInt32(t *testutil.TestRunner) { + var x struct { + before int32 + i int32 + after int32 + } + x.before = magic32 + x.after = magic32 + var j int32 + for delta := int32(1); delta+delta > delta; delta += delta { + k := SwapInt32(&x.i, delta) + if x.i != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + j = delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestSwapInt32Method(t *testutil.TestRunner) { + var x struct { + before int32 + i Int32 + after int32 + } + x.before = magic32 + x.after = magic32 + var j int32 + for delta := int32(1); delta+delta > delta; delta += delta { + k := x.i.Swap(delta) + if x.i.Load() != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + j = delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestSwapUint32(t *testutil.TestRunner) { + var x struct { + before uint32 + i uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + var j uint32 + for delta := uint32(1); delta+delta > delta; delta += delta { + k := SwapUint32(&x.i, delta) + if x.i != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + j = delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestSwapUint32Method(t *testutil.TestRunner) { + var x struct { + before uint32 + i Uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + var j uint32 + for delta := uint32(1); delta+delta > delta; delta += delta { + k := x.i.Swap(delta) + if x.i.Load() != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + j = delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestSwapInt64(t *testutil.TestRunner) { + var x struct { + before int64 + i int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + var j int64 + for delta := int64(1); delta+delta > delta; delta += delta { + k := SwapInt64(&x.i, delta) + if x.i != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + j = delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestSwapInt64Method(t *testutil.TestRunner) { + var x struct { + before int64 + i Int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + var j int64 + for delta := int64(1); delta+delta > delta; delta += delta { + k := x.i.Swap(delta) + if x.i.Load() != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + j = delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestSwapUint64(t *testutil.TestRunner) { + var x struct { + before uint64 + i uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + var j uint64 + for delta := uint64(1); delta+delta > delta; delta += delta { + k := SwapUint64(&x.i, delta) + if x.i != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + j = delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestSwapUint64Method(t *testutil.TestRunner) { + var x struct { + before uint64 + i Uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + var j uint64 + for delta := uint64(1); delta+delta > delta; delta += delta { + k := x.i.Swap(delta) + if x.i.Load() != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + j = delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestSwapUintptr(t *testutil.TestRunner) { + var x struct { + before uintptr + i uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + var j uintptr + for delta := uintptr(1); delta+delta > delta; delta += delta { + k := SwapUintptr(&x.i, delta) + if x.i != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + j = delta + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestSwapUintptrMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + var j uintptr + for delta := uintptr(1); delta+delta > delta; delta += delta { + k := x.i.Swap(delta) + if x.i.Load() != delta || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + j = delta + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +var global [1024]byte + +func testPointers() []unsafe.Pointer { + var pointers []unsafe.Pointer + // globals + for i := 0; i < 10; i++ { + pointers = append(pointers, unsafe.Pointer(&global[1< delta; delta += delta { + k := AddInt32(&x.i, delta) + j += delta + if x.i != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestAddInt32Method(t *testutil.TestRunner) { + var x struct { + before int32 + i Int32 + after int32 + } + x.before = magic32 + x.after = magic32 + var j int32 + for delta := int32(1); delta+delta > delta; delta += delta { + k := x.i.Add(delta) + j += delta + if x.i.Load() != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestAddUint32(t *testutil.TestRunner) { + var x struct { + before uint32 + i uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + var j uint32 + for delta := uint32(1); delta+delta > delta; delta += delta { + k := AddUint32(&x.i, delta) + j += delta + if x.i != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestAddUint32Method(t *testutil.TestRunner) { + var x struct { + before uint32 + i Uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + var j uint32 + for delta := uint32(1); delta+delta > delta; delta += delta { + k := x.i.Add(delta) + j += delta + if x.i.Load() != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestAddInt64(t *testutil.TestRunner) { + var x struct { + before int64 + i int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + var j int64 + for delta := int64(1); delta+delta > delta; delta += delta { + k := AddInt64(&x.i, delta) + j += delta + if x.i != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestAddInt64Method(t *testutil.TestRunner) { + var x struct { + before int64 + i Int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + var j int64 + for delta := int64(1); delta+delta > delta; delta += delta { + k := x.i.Add(delta) + j += delta + if x.i.Load() != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestAddUint64(t *testutil.TestRunner) { + var x struct { + before uint64 + i uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + var j uint64 + for delta := uint64(1); delta+delta > delta; delta += delta { + k := AddUint64(&x.i, delta) + j += delta + if x.i != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestAddUint64Method(t *testutil.TestRunner) { + var x struct { + before uint64 + i Uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + var j uint64 + for delta := uint64(1); delta+delta > delta; delta += delta { + k := x.i.Add(delta) + j += delta + if x.i.Load() != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestAddUintptr(t *testutil.TestRunner) { + var x struct { + before uintptr + i uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + var j uintptr + for delta := uintptr(1); delta+delta > delta; delta += delta { + k := AddUintptr(&x.i, delta) + j += delta + if x.i != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestAddUintptrMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + var j uintptr + for delta := uintptr(1); delta+delta > delta; delta += delta { + k := x.i.Add(delta) + j += delta + if x.i.Load() != j || k != j { + t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i.Load(), j, k) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestCompareAndSwapInt32(t *testutil.TestRunner) { + var x struct { + before int32 + i int32 + after int32 + } + x.before = magic32 + x.after = magic32 + for val := int32(1); val+val > val; val += val { + x.i = val + if !CompareAndSwapInt32(&x.i, val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + x.i = val + 1 + if CompareAndSwapInt32(&x.i, val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestCompareAndSwapInt32Method(t *testutil.TestRunner) { + var x struct { + before int32 + i Int32 + after int32 + } + x.before = magic32 + x.after = magic32 + for val := int32(1); val+val > val; val += val { + x.i.Store(val) + if !x.i.CompareAndSwap(val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + x.i.Store(val + 1) + if x.i.CompareAndSwap(val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestCompareAndSwapUint32(t *testutil.TestRunner) { + var x struct { + before uint32 + i uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + for val := uint32(1); val+val > val; val += val { + x.i = val + if !CompareAndSwapUint32(&x.i, val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + x.i = val + 1 + if CompareAndSwapUint32(&x.i, val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestCompareAndSwapUint32Method(t *testutil.TestRunner) { + var x struct { + before uint32 + i Uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + for val := uint32(1); val+val > val; val += val { + x.i.Store(val) + if !x.i.CompareAndSwap(val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + x.i.Store(val + 1) + if x.i.CompareAndSwap(val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestCompareAndSwapInt64(t *testutil.TestRunner) { + var x struct { + before int64 + i int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + for val := int64(1); val+val > val; val += val { + x.i = val + if !CompareAndSwapInt64(&x.i, val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + x.i = val + 1 + if CompareAndSwapInt64(&x.i, val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestCompareAndSwapInt64Method(t *testutil.TestRunner) { + var x struct { + before int64 + i Int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + for val := int64(1); val+val > val; val += val { + x.i.Store(val) + if !x.i.CompareAndSwap(val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + x.i.Store(val + 1) + if x.i.CompareAndSwap(val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func testCompareAndSwapUint64(t testing.TB, cas func(*uint64, uint64, uint64) bool) { + var x struct { + before uint64 + i uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + for val := uint64(1); val+val > val; val += val { + x.i = val + if !cas(&x.i, val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + x.i = val + 1 + if cas(&x.i, val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestCompareAndSwapUint64(t *testutil.TestRunner) { + testCompareAndSwapUint64(t, CompareAndSwapUint64) +} + +func TestCompareAndSwapUint64Method(t *testutil.TestRunner) { + var x struct { + before uint64 + i Uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + for val := uint64(1); val+val > val; val += val { + x.i.Store(val) + if !x.i.CompareAndSwap(val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + x.i.Store(val + 1) + if x.i.CompareAndSwap(val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestCompareAndSwapUintptr(t *testutil.TestRunner) { + var x struct { + before uintptr + i uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + for val := uintptr(1); val+val > val; val += val { + x.i = val + if !CompareAndSwapUintptr(&x.i, val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + x.i = val + 1 + if CompareAndSwapUintptr(&x.i, val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestCompareAndSwapUintptrMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + for val := uintptr(1); val+val > val; val += val { + x.i.Store(val) + if !x.i.CompareAndSwap(val, val+1) { + t.Fatalf("should have swapped %#x %#x", val, val+1) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + x.i.Store(val + 1) + if x.i.CompareAndSwap(val, val+2) { + t.Fatalf("should not have swapped %#x %#x", val, val+2) + } + if x.i.Load() != val+1 { + t.Fatalf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i.Load(), val+1) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, uintptr(magicptr), uintptr(magicptr)) + } +} + +func TestCompareAndSwapPointer(t *testutil.TestRunner) { + var x struct { + before uintptr + i unsafe.Pointer + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + q := unsafe.Pointer(new(byte)) + for _, p := range testPointers() { + x.i = p + if !CompareAndSwapPointer(&x.i, p, q) { + t.Fatalf("should have swapped %p %p", p, q) + } + if x.i != q { + t.Fatalf("wrong x.i after swap: x.i=%p want %p", x.i, q) + } + if CompareAndSwapPointer(&x.i, p, nil) { + t.Fatalf("should not have swapped %p nil", p) + } + if x.i != q { + t.Fatalf("wrong x.i after swap: x.i=%p want %p", x.i, q) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestCompareAndSwapPointerMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Pointer[byte] + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + q := new(byte) + for _, p := range testPointers() { + p := (*byte)(p) + x.i.Store(p) + if !x.i.CompareAndSwap(p, q) { + t.Fatalf("should have swapped %p %p", p, q) + } + if x.i.Load() != q { + t.Fatalf("wrong x.i after swap: x.i=%p want %p", x.i.Load(), q) + } + if x.i.CompareAndSwap(p, nil) { + t.Fatalf("should not have swapped %p nil", p) + } + if x.i.Load() != q { + t.Fatalf("wrong x.i after swap: x.i=%p want %p", x.i.Load(), q) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestLoadInt32(t *testutil.TestRunner) { + var x struct { + before int32 + i int32 + after int32 + } + x.before = magic32 + x.after = magic32 + for delta := int32(1); delta+delta > delta; delta += delta { + k := LoadInt32(&x.i) + if k != x.i { + t.Fatalf("delta=%d i=%d k=%d", delta, x.i, k) + } + x.i += delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestLoadInt32Method(t *testutil.TestRunner) { + var x struct { + before int32 + i Int32 + after int32 + } + x.before = magic32 + x.after = magic32 + want := int32(0) + for delta := int32(1); delta+delta > delta; delta += delta { + k := x.i.Load() + if k != want { + t.Fatalf("delta=%d i=%d k=%d want=%d", delta, x.i.Load(), k, want) + } + x.i.Store(k + delta) + want = k + delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestLoadUint32(t *testutil.TestRunner) { + var x struct { + before uint32 + i uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + for delta := uint32(1); delta+delta > delta; delta += delta { + k := LoadUint32(&x.i) + if k != x.i { + t.Fatalf("delta=%d i=%d k=%d", delta, x.i, k) + } + x.i += delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestLoadUint32Method(t *testutil.TestRunner) { + var x struct { + before uint32 + i Uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + want := uint32(0) + for delta := uint32(1); delta+delta > delta; delta += delta { + k := x.i.Load() + if k != want { + t.Fatalf("delta=%d i=%d k=%d want=%d", delta, x.i.Load(), k, want) + } + x.i.Store(k + delta) + want = k + delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestLoadInt64(t *testutil.TestRunner) { + var x struct { + before int64 + i int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + for delta := int64(1); delta+delta > delta; delta += delta { + k := LoadInt64(&x.i) + if k != x.i { + t.Fatalf("delta=%d i=%d k=%d", delta, x.i, k) + } + x.i += delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestLoadInt64Method(t *testutil.TestRunner) { + var x struct { + before int64 + i Int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + want := int64(0) + for delta := int64(1); delta+delta > delta; delta += delta { + k := x.i.Load() + if k != want { + t.Fatalf("delta=%d i=%d k=%d want=%d", delta, x.i.Load(), k, want) + } + x.i.Store(k + delta) + want = k + delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestLoadUint64(t *testutil.TestRunner) { + var x struct { + before uint64 + i uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + for delta := uint64(1); delta+delta > delta; delta += delta { + k := LoadUint64(&x.i) + if k != x.i { + t.Fatalf("delta=%d i=%d k=%d", delta, x.i, k) + } + x.i += delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestLoadUint64Method(t *testutil.TestRunner) { + var x struct { + before uint64 + i Uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + want := uint64(0) + for delta := uint64(1); delta+delta > delta; delta += delta { + k := x.i.Load() + if k != want { + t.Fatalf("delta=%d i=%d k=%d want=%d", delta, x.i.Load(), k, want) + } + x.i.Store(k + delta) + want = k + delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestLoadUintptr(t *testutil.TestRunner) { + var x struct { + before uintptr + i uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + for delta := uintptr(1); delta+delta > delta; delta += delta { + k := LoadUintptr(&x.i) + if k != x.i { + t.Fatalf("delta=%d i=%d k=%d", delta, x.i, k) + } + x.i += delta + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestLoadUintptrMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + want := uintptr(0) + for delta := uintptr(1); delta+delta > delta; delta += delta { + k := x.i.Load() + if k != want { + t.Fatalf("delta=%d i=%d k=%d want=%d", delta, x.i.Load(), k, want) + } + x.i.Store(k + delta) + want = k + delta + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestLoadPointer(t *testutil.TestRunner) { + var x struct { + before uintptr + i unsafe.Pointer + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + for _, p := range testPointers() { + x.i = p + k := LoadPointer(&x.i) + if k != p { + t.Fatalf("p=%x k=%x", p, k) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestLoadPointerMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Pointer[byte] + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + for _, p := range testPointers() { + p := (*byte)(p) + x.i.Store(p) + k := x.i.Load() + if k != p { + t.Fatalf("p=%x k=%x", p, k) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestStoreInt32(t *testutil.TestRunner) { + var x struct { + before int32 + i int32 + after int32 + } + x.before = magic32 + x.after = magic32 + v := int32(0) + for delta := int32(1); delta+delta > delta; delta += delta { + StoreInt32(&x.i, v) + if x.i != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i, v) + } + v += delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestStoreInt32Method(t *testutil.TestRunner) { + var x struct { + before int32 + i Int32 + after int32 + } + x.before = magic32 + x.after = magic32 + v := int32(0) + for delta := int32(1); delta+delta > delta; delta += delta { + x.i.Store(v) + if x.i.Load() != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i.Load(), v) + } + v += delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestStoreUint32(t *testutil.TestRunner) { + var x struct { + before uint32 + i uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + v := uint32(0) + for delta := uint32(1); delta+delta > delta; delta += delta { + StoreUint32(&x.i, v) + if x.i != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i, v) + } + v += delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestStoreUint32Method(t *testutil.TestRunner) { + var x struct { + before uint32 + i Uint32 + after uint32 + } + x.before = magic32 + x.after = magic32 + v := uint32(0) + for delta := uint32(1); delta+delta > delta; delta += delta { + x.i.Store(v) + if x.i.Load() != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i.Load(), v) + } + v += delta + } + if x.before != magic32 || x.after != magic32 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32) + } +} + +func TestStoreInt64(t *testutil.TestRunner) { + var x struct { + before int64 + i int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + v := int64(0) + for delta := int64(1); delta+delta > delta; delta += delta { + StoreInt64(&x.i, v) + if x.i != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i, v) + } + v += delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestStoreInt64Method(t *testutil.TestRunner) { + var x struct { + before int64 + i Int64 + after int64 + } + magic64 := int64(magic64) + x.before = magic64 + x.after = magic64 + v := int64(0) + for delta := int64(1); delta+delta > delta; delta += delta { + x.i.Store(v) + if x.i.Load() != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i.Load(), v) + } + v += delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestStoreUint64(t *testutil.TestRunner) { + var x struct { + before uint64 + i uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + v := uint64(0) + for delta := uint64(1); delta+delta > delta; delta += delta { + StoreUint64(&x.i, v) + if x.i != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i, v) + } + v += delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestStoreUint64Method(t *testutil.TestRunner) { + var x struct { + before uint64 + i Uint64 + after uint64 + } + magic64 := uint64(magic64) + x.before = magic64 + x.after = magic64 + v := uint64(0) + for delta := uint64(1); delta+delta > delta; delta += delta { + x.i.Store(v) + if x.i.Load() != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i.Load(), v) + } + v += delta + } + if x.before != magic64 || x.after != magic64 { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic64, magic64) + } +} + +func TestStoreUintptr(t *testutil.TestRunner) { + var x struct { + before uintptr + i uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + v := uintptr(0) + for delta := uintptr(1); delta+delta > delta; delta += delta { + StoreUintptr(&x.i, v) + if x.i != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i, v) + } + v += delta + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestStoreUintptrMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Uintptr + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + v := uintptr(0) + for delta := uintptr(1); delta+delta > delta; delta += delta { + x.i.Store(v) + if x.i.Load() != v { + t.Fatalf("delta=%d i=%d v=%d", delta, x.i.Load(), v) + } + v += delta + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestStorePointer(t *testutil.TestRunner) { + var x struct { + before uintptr + i unsafe.Pointer + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + for _, p := range testPointers() { + StorePointer(&x.i, p) + if x.i != p { + t.Fatalf("x.i=%p p=%p", x.i, p) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +func TestStorePointerMethod(t *testutil.TestRunner) { + var x struct { + before uintptr + i Pointer[byte] + after uintptr + } + var m uint64 = magic64 + magicptr := uintptr(m) + x.before = magicptr + x.after = magicptr + for _, p := range testPointers() { + p := (*byte)(p) + x.i.Store(p) + if x.i.Load() != p { + t.Fatalf("x.i=%p p=%p", x.i.Load(), p) + } + } + if x.before != magicptr || x.after != magicptr { + t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr) + } +} + +// Tests of correct behavior, with contention. +// (Is the function atomic?) +// +// For each function, we write a "hammer" function that repeatedly +// uses the atomic operation to add 1 to a value. After running +// multiple hammers in parallel, check that we end with the correct +// total. +// Swap can't add 1, so it uses a different scheme. +// The functions repeatedly generate a pseudo-random number such that +// low bits are equal to high bits, swap, check that the old value +// has low and high bits equal. + +var hammer32 = map[string]func(*uint32, int){ + "SwapInt32": hammerSwapInt32, + "SwapUint32": hammerSwapUint32, + "SwapUintptr": hammerSwapUintptr32, + "AddInt32": hammerAddInt32, + "AddUint32": hammerAddUint32, + "AddUintptr": hammerAddUintptr32, + "CompareAndSwapInt32": hammerCompareAndSwapInt32, + "CompareAndSwapUint32": hammerCompareAndSwapUint32, + "CompareAndSwapUintptr": hammerCompareAndSwapUintptr32, + + "SwapInt32Method": hammerSwapInt32Method, + "SwapUint32Method": hammerSwapUint32Method, + "SwapUintptrMethod": hammerSwapUintptr32Method, + "AddInt32Method": hammerAddInt32Method, + "AddUint32Method": hammerAddUint32Method, + "AddUintptrMethod": hammerAddUintptr32Method, + "CompareAndSwapInt32Method": hammerCompareAndSwapInt32Method, + "CompareAndSwapUint32Method": hammerCompareAndSwapUint32Method, + "CompareAndSwapUintptrMethod": hammerCompareAndSwapUintptr32Method, +} + +func init() { + var v uint64 = 1 << 50 + if uintptr(v) != 0 { + // 64-bit system; clear uintptr tests + delete(hammer32, "SwapUintptr") + delete(hammer32, "AddUintptr") + delete(hammer32, "CompareAndSwapUintptr") + delete(hammer32, "SwapUintptrMethod") + delete(hammer32, "AddUintptrMethod") + delete(hammer32, "CompareAndSwapUintptrMethod") + } +} + +func hammerSwapInt32(uaddr *uint32, count int) { + addr := (*int32)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint32(seed+i)<<16 | uint32(seed+i)<<16>>16 + old := uint32(SwapInt32(addr, int32(new))) + if old>>16 != old<<16>>16 { + panic(fmt.Sprintf("SwapInt32 is not atomic: %v", old)) + } + } +} + +func hammerSwapInt32Method(uaddr *uint32, count int) { + addr := (*Int32)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint32(seed+i)<<16 | uint32(seed+i)<<16>>16 + old := uint32(addr.Swap(int32(new))) + if old>>16 != old<<16>>16 { + panic(fmt.Sprintf("SwapInt32 is not atomic: %v", old)) + } + } +} + +func hammerSwapUint32(addr *uint32, count int) { + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint32(seed+i)<<16 | uint32(seed+i)<<16>>16 + old := SwapUint32(addr, new) + if old>>16 != old<<16>>16 { + panic(fmt.Sprintf("SwapUint32 is not atomic: %v", old)) + } + } +} + +func hammerSwapUint32Method(uaddr *uint32, count int) { + addr := (*Uint32)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint32(seed+i)<<16 | uint32(seed+i)<<16>>16 + old := addr.Swap(new) + if old>>16 != old<<16>>16 { + panic(fmt.Sprintf("SwapUint32 is not atomic: %v", old)) + } + } +} + +func hammerSwapUintptr32(uaddr *uint32, count int) { + // only safe when uintptr is 32-bit. + // not called on 64-bit systems. + addr := (*uintptr)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uintptr(seed+i)<<16 | uintptr(seed+i)<<16>>16 + old := SwapUintptr(addr, new) + if old>>16 != old<<16>>16 { + panic(fmt.Sprintf("SwapUintptr is not atomic: %#08x", old)) + } + } +} + +func hammerSwapUintptr32Method(uaddr *uint32, count int) { + // only safe when uintptr is 32-bit. + // not called on 64-bit systems. + addr := (*Uintptr)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uintptr(seed+i)<<16 | uintptr(seed+i)<<16>>16 + old := addr.Swap(new) + if old>>16 != old<<16>>16 { + panic(fmt.Sprintf("Uintptr.Swap is not atomic: %#08x", old)) + } + } +} + +func hammerAddInt32(uaddr *uint32, count int) { + addr := (*int32)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + AddInt32(addr, 1) + } +} + +func hammerAddInt32Method(uaddr *uint32, count int) { + addr := (*Int32)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + addr.Add(1) + } +} + +func hammerAddUint32(addr *uint32, count int) { + for i := 0; i < count; i++ { + AddUint32(addr, 1) + } +} + +func hammerAddUint32Method(uaddr *uint32, count int) { + addr := (*Uint32)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + addr.Add(1) + } +} + +func hammerAddUintptr32(uaddr *uint32, count int) { + // only safe when uintptr is 32-bit. + // not called on 64-bit systems. + addr := (*uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + AddUintptr(addr, 1) + } +} + +func hammerAddUintptr32Method(uaddr *uint32, count int) { + // only safe when uintptr is 32-bit. + // not called on 64-bit systems. + addr := (*Uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + addr.Add(1) + } +} + +func hammerCompareAndSwapInt32(uaddr *uint32, count int) { + addr := (*int32)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := LoadInt32(addr) + if CompareAndSwapInt32(addr, v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapInt32Method(uaddr *uint32, count int) { + addr := (*Int32)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := addr.Load() + if addr.CompareAndSwap(v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUint32(addr *uint32, count int) { + for i := 0; i < count; i++ { + for { + v := LoadUint32(addr) + if CompareAndSwapUint32(addr, v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUint32Method(uaddr *uint32, count int) { + addr := (*Uint32)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := addr.Load() + if addr.CompareAndSwap(v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUintptr32(uaddr *uint32, count int) { + // only safe when uintptr is 32-bit. + // not called on 64-bit systems. + addr := (*uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := LoadUintptr(addr) + if CompareAndSwapUintptr(addr, v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUintptr32Method(uaddr *uint32, count int) { + // only safe when uintptr is 32-bit. + // not called on 64-bit systems. + addr := (*Uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := addr.Load() + if addr.CompareAndSwap(v, v+1) { + break + } + } + } +} + +func TestHammer32(t *testutil.TestRunner) { + const p = 4 + n := 100000 + if short { + n = 1000 + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(p)) + + for name, testf := range hammer32 { + c := make(chan int) + var val uint32 + for i := 0; i < p; i++ { + go func() { + defer func() { + if err := recover(); err != nil { + t.Error(err.(string)) + } + c <- 1 + }() + testf(&val, n) + }() + } + for i := 0; i < p; i++ { + <-c + } + if !strings.HasPrefix(name, "Swap") && val != uint32(n)*p { + t.Fatalf("%s: val=%d want %d", name, val, n*p) + } + } +} + +var hammer64 = map[string]func(*uint64, int){ + "SwapInt64": hammerSwapInt64, + "SwapUint64": hammerSwapUint64, + "SwapUintptr": hammerSwapUintptr64, + "AddInt64": hammerAddInt64, + "AddUint64": hammerAddUint64, + "AddUintptr": hammerAddUintptr64, + "CompareAndSwapInt64": hammerCompareAndSwapInt64, + "CompareAndSwapUint64": hammerCompareAndSwapUint64, + "CompareAndSwapUintptr": hammerCompareAndSwapUintptr64, + + "SwapInt64Method": hammerSwapInt64Method, + "SwapUint64Method": hammerSwapUint64Method, + "SwapUintptrMethod": hammerSwapUintptr64Method, + "AddInt64Method": hammerAddInt64Method, + "AddUint64Method": hammerAddUint64Method, + "AddUintptrMethod": hammerAddUintptr64Method, + "CompareAndSwapInt64Method": hammerCompareAndSwapInt64Method, + "CompareAndSwapUint64Method": hammerCompareAndSwapUint64Method, + "CompareAndSwapUintptrMethod": hammerCompareAndSwapUintptr64Method, +} + +func init() { + var v uint64 = 1 << 50 + if uintptr(v) == 0 { + // 32-bit system; clear uintptr tests + delete(hammer64, "SwapUintptr") + delete(hammer64, "SwapUintptrMethod") + delete(hammer64, "AddUintptr") + delete(hammer64, "AddUintptrMethod") + delete(hammer64, "CompareAndSwapUintptr") + delete(hammer64, "CompareAndSwapUintptrMethod") + } +} + +func hammerSwapInt64(uaddr *uint64, count int) { + addr := (*int64)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint64(seed+i)<<32 | uint64(seed+i)<<32>>32 + old := uint64(SwapInt64(addr, int64(new))) + if old>>32 != old<<32>>32 { + panic(fmt.Sprintf("SwapInt64 is not atomic: %v", old)) + } + } +} + +func hammerSwapInt64Method(uaddr *uint64, count int) { + addr := (*Int64)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint64(seed+i)<<32 | uint64(seed+i)<<32>>32 + old := uint64(addr.Swap(int64(new))) + if old>>32 != old<<32>>32 { + panic(fmt.Sprintf("SwapInt64 is not atomic: %v", old)) + } + } +} + +func hammerSwapUint64(addr *uint64, count int) { + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint64(seed+i)<<32 | uint64(seed+i)<<32>>32 + old := SwapUint64(addr, new) + if old>>32 != old<<32>>32 { + panic(fmt.Sprintf("SwapUint64 is not atomic: %v", old)) + } + } +} + +func hammerSwapUint64Method(uaddr *uint64, count int) { + addr := (*Uint64)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uint64(seed+i)<<32 | uint64(seed+i)<<32>>32 + old := addr.Swap(new) + if old>>32 != old<<32>>32 { + panic(fmt.Sprintf("SwapUint64 is not atomic: %v", old)) + } + } +} + +const arch32 = unsafe.Sizeof(uintptr(0)) == 4 + +func hammerSwapUintptr64(uaddr *uint64, count int) { + // only safe when uintptr is 64-bit. + // not called on 32-bit systems. + if !arch32 { + addr := (*uintptr)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uintptr(seed+i)<<32 | uintptr(seed+i)<<32>>32 + old := SwapUintptr(addr, new) + if old>>32 != old<<32>>32 { + panic(fmt.Sprintf("SwapUintptr is not atomic: %v", old)) + } + } + } +} + +func hammerSwapUintptr64Method(uaddr *uint64, count int) { + // only safe when uintptr is 64-bit. + // not called on 32-bit systems. + if !arch32 { + addr := (*Uintptr)(unsafe.Pointer(uaddr)) + seed := int(uintptr(unsafe.Pointer(&count))) + for i := 0; i < count; i++ { + new := uintptr(seed+i)<<32 | uintptr(seed+i)<<32>>32 + old := addr.Swap(new) + if old>>32 != old<<32>>32 { + panic(fmt.Sprintf("SwapUintptr is not atomic: %v", old)) + } + } + } +} + +func hammerAddInt64(uaddr *uint64, count int) { + addr := (*int64)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + AddInt64(addr, 1) + } +} + +func hammerAddInt64Method(uaddr *uint64, count int) { + addr := (*Int64)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + addr.Add(1) + } +} + +func hammerAddUint64(addr *uint64, count int) { + for i := 0; i < count; i++ { + AddUint64(addr, 1) + } +} + +func hammerAddUint64Method(uaddr *uint64, count int) { + addr := (*Uint64)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + addr.Add(1) + } +} + +func hammerAddUintptr64(uaddr *uint64, count int) { + // only safe when uintptr is 64-bit. + // not called on 32-bit systems. + addr := (*uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + AddUintptr(addr, 1) + } +} + +func hammerAddUintptr64Method(uaddr *uint64, count int) { + // only safe when uintptr is 64-bit. + // not called on 32-bit systems. + addr := (*Uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + addr.Add(1) + } +} + +func hammerCompareAndSwapInt64(uaddr *uint64, count int) { + addr := (*int64)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := LoadInt64(addr) + if CompareAndSwapInt64(addr, v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapInt64Method(uaddr *uint64, count int) { + addr := (*Int64)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := addr.Load() + if addr.CompareAndSwap(v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUint64(addr *uint64, count int) { + for i := 0; i < count; i++ { + for { + v := LoadUint64(addr) + if CompareAndSwapUint64(addr, v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUint64Method(uaddr *uint64, count int) { + addr := (*Uint64)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := addr.Load() + if addr.CompareAndSwap(v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUintptr64(uaddr *uint64, count int) { + // only safe when uintptr is 64-bit. + // not called on 32-bit systems. + addr := (*uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := LoadUintptr(addr) + if CompareAndSwapUintptr(addr, v, v+1) { + break + } + } + } +} + +func hammerCompareAndSwapUintptr64Method(uaddr *uint64, count int) { + // only safe when uintptr is 64-bit. + // not called on 32-bit systems. + addr := (*Uintptr)(unsafe.Pointer(uaddr)) + for i := 0; i < count; i++ { + for { + v := addr.Load() + if addr.CompareAndSwap(v, v+1) { + break + } + } + } +} + +func TestHammer64(t *testutil.TestRunner) { + const p = 4 + n := 100000 + if short { + n = 1000 + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(p)) + + for name, testf := range hammer64 { + c := make(chan int) + var val uint64 + for i := 0; i < p; i++ { + go func() { + defer func() { + if err := recover(); err != nil { + t.Error(err.(string)) + } + c <- 1 + }() + testf(&val, n) + }() + } + for i := 0; i < p; i++ { + <-c + } + if !strings.HasPrefix(name, "Swap") && val != uint64(n)*p { + t.Fatalf("%s: val=%d want %d", name, val, n*p) + } + } +} + +func hammerStoreLoadInt32(t testing.TB, paddr unsafe.Pointer) { + addr := (*int32)(paddr) + v := LoadInt32(addr) + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Int32: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + StoreInt32(addr, new) +} + +func hammerStoreLoadInt32Method(t testing.TB, paddr unsafe.Pointer) { + addr := (*int32)(paddr) + v := LoadInt32(addr) + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Int32: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + StoreInt32(addr, new) +} + +func hammerStoreLoadUint32(t testing.TB, paddr unsafe.Pointer) { + addr := (*uint32)(paddr) + v := LoadUint32(addr) + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Uint32: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + StoreUint32(addr, new) +} + +func hammerStoreLoadUint32Method(t testing.TB, paddr unsafe.Pointer) { + addr := (*Uint32)(paddr) + v := addr.Load() + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Uint32: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + addr.Store(new) +} + +func hammerStoreLoadInt64(t testing.TB, paddr unsafe.Pointer) { + addr := (*int64)(paddr) + v := LoadInt64(addr) + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Int64: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<32 + StoreInt64(addr, new) +} + +func hammerStoreLoadInt64Method(t testing.TB, paddr unsafe.Pointer) { + addr := (*Int64)(paddr) + v := addr.Load() + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Int64: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<32 + addr.Store(new) +} + +func hammerStoreLoadUint64(t testing.TB, paddr unsafe.Pointer) { + addr := (*uint64)(paddr) + v := LoadUint64(addr) + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Uint64: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<32 + StoreUint64(addr, new) +} + +func hammerStoreLoadUint64Method(t testing.TB, paddr unsafe.Pointer) { + addr := (*Uint64)(paddr) + v := addr.Load() + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Uint64: %#x != %#x", vlo, vhi) + } + new := v + 1 + 1<<32 + addr.Store(new) +} + +func hammerStoreLoadUintptr(t testing.TB, paddr unsafe.Pointer) { + addr := (*uintptr)(paddr) + v := LoadUintptr(addr) + new := v + if arch32 { + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Uintptr: %#x != %#x", vlo, vhi) + } + new = v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + } else { + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Uintptr: %#x != %#x", vlo, vhi) + } + inc := uint64(1 + 1<<32) + new = v + uintptr(inc) + } + StoreUintptr(addr, new) +} + +//go:nocheckptr +func hammerStoreLoadUintptrMethod(t testing.TB, paddr unsafe.Pointer) { + addr := (*Uintptr)(paddr) + v := addr.Load() + new := v + if arch32 { + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Uintptr: %#x != %#x", vlo, vhi) + } + new = v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + } else { + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Uintptr: %#x != %#x", vlo, vhi) + } + inc := uint64(1 + 1<<32) + new = v + uintptr(inc) + } + addr.Store(new) +} + +// This code is just testing that LoadPointer/StorePointer operate +// atomically; it's not actually calculating pointers. +// +//go:nocheckptr +func hammerStoreLoadPointer(t testing.TB, paddr unsafe.Pointer) { + addr := (*unsafe.Pointer)(paddr) + v := uintptr(LoadPointer(addr)) + new := v + if arch32 { + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Pointer: %#x != %#x", vlo, vhi) + } + new = v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + } else { + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Pointer: %#x != %#x", vlo, vhi) + } + inc := uint64(1 + 1<<32) + new = v + uintptr(inc) + } + StorePointer(addr, unsafe.Pointer(new)) +} + +// This code is just testing that LoadPointer/StorePointer operate +// atomically; it's not actually calculating pointers. +// +//go:nocheckptr +func hammerStoreLoadPointerMethod(t testing.TB, paddr unsafe.Pointer) { + addr := (*Pointer[byte])(paddr) + v := uintptr(unsafe.Pointer(addr.Load())) + new := v + if arch32 { + vlo := v & ((1 << 16) - 1) + vhi := v >> 16 + if vlo != vhi { + t.Fatalf("Pointer: %#x != %#x", vlo, vhi) + } + new = v + 1 + 1<<16 + if vlo == 1e4 { + new = 0 + } + } else { + vlo := v & ((1 << 32) - 1) + vhi := v >> 32 + if vlo != vhi { + t.Fatalf("Pointer: %#x != %#x", vlo, vhi) + } + inc := uint64(1 + 1<<32) + new = v + uintptr(inc) + } + addr.Store((*byte)(unsafe.Pointer(new))) +} + +func TestHammerStoreLoad(t *testutil.TestRunner) { + tests := []func(testing.TB, unsafe.Pointer){ + hammerStoreLoadInt32, hammerStoreLoadUint32, + hammerStoreLoadUintptr, hammerStoreLoadPointer, + hammerStoreLoadInt32Method, hammerStoreLoadUint32Method, + hammerStoreLoadUintptrMethod, hammerStoreLoadPointerMethod, + hammerStoreLoadInt64, hammerStoreLoadUint64, + hammerStoreLoadInt64Method, hammerStoreLoadUint64Method, + } + n := int(1e6) + if short { + n = int(1e4) + } + const procs = 8 + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(procs)) + // Disable the GC because hammerStoreLoadPointer invokes + // write barriers on values that aren't real pointers. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) + // Ensure any in-progress GC is finished. + runtime.GC() + for _, tt := range tests { + c := make(chan int) + var val uint64 + for p := 0; p < procs; p++ { + go func() { + for i := 0; i < n; i++ { + tt(t, unsafe.Pointer(&val)) + } + c <- 1 + }() + } + for p := 0; p < procs; p++ { + <-c + } + } +} + +func TestStoreLoadSeqCst32(t *testutil.TestRunner) { + if runtime.NumCPU() == 1 { + t.Skipf("Skipping test on %v processor machine", runtime.NumCPU()) + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + N := int32(1e3) + if short { + N = int32(1e2) + } + c := make(chan bool, 2) + X := [2]int32{} + ack := [2][3]int32{{-1, -1, -1}, {-1, -1, -1}} + for p := 0; p < 2; p++ { + go func(me int) { + he := 1 - me + for i := int32(1); i < N; i++ { + StoreInt32(&X[me], i) + my := LoadInt32(&X[he]) + StoreInt32(&ack[me][i%3], my) + for w := 1; LoadInt32(&ack[he][i%3]) == -1; w++ { + if w%1000 == 0 { + runtime.Gosched() + } + } + his := LoadInt32(&ack[he][i%3]) + if (my != i && my != i-1) || (his != i && his != i-1) { + t.Errorf("invalid values: %d/%d (%d)", my, his, i) + break + } + if my != i && his != i { + t.Errorf("store/load are not sequentially consistent: %d/%d (%d)", my, his, i) + break + } + StoreInt32(&ack[me][(i-1)%3], -1) + } + c <- true + }(p) + } + <-c + <-c +} + +func TestStoreLoadSeqCst64(t *testutil.TestRunner) { + if runtime.NumCPU() == 1 { + t.Skipf("Skipping test on %v processor machine", runtime.NumCPU()) + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + N := int64(1e3) + if short { + N = int64(1e2) + } + c := make(chan bool, 2) + X := [2]int64{} + ack := [2][3]int64{{-1, -1, -1}, {-1, -1, -1}} + for p := 0; p < 2; p++ { + go func(me int) { + he := 1 - me + for i := int64(1); i < N; i++ { + StoreInt64(&X[me], i) + my := LoadInt64(&X[he]) + StoreInt64(&ack[me][i%3], my) + for w := 1; LoadInt64(&ack[he][i%3]) == -1; w++ { + if w%1000 == 0 { + runtime.Gosched() + } + } + his := LoadInt64(&ack[he][i%3]) + if (my != i && my != i-1) || (his != i && his != i-1) { + t.Errorf("invalid values: %d/%d (%d)", my, his, i) + break + } + if my != i && his != i { + t.Errorf("store/load are not sequentially consistent: %d/%d (%d)", my, his, i) + break + } + StoreInt64(&ack[me][(i-1)%3], -1) + } + c <- true + }(p) + } + <-c + <-c +} + +func TestStoreLoadRelAcq32(t *testutil.TestRunner) { + if runtime.NumCPU() == 1 { + t.Skipf("Skipping test on %v processor machine", runtime.NumCPU()) + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + N := int32(1e3) + if short { + N = int32(1e2) + } + c := make(chan bool, 2) + type Data struct { + signal int32 + pad1 [128]int8 + data1 int32 + pad2 [128]int8 + data2 float32 + } + var X Data + for p := int32(0); p < 2; p++ { + go func(p int32) { + for i := int32(1); i < N; i++ { + if (i+p)%2 == 0 { + X.data1 = i + X.data2 = float32(i) + StoreInt32(&X.signal, i) + } else { + for w := 1; LoadInt32(&X.signal) != i; w++ { + if w%1000 == 0 { + runtime.Gosched() + } + } + d1 := X.data1 + d2 := X.data2 + if d1 != i || d2 != float32(i) { + t.Errorf("incorrect data: %d/%g (%d)", d1, d2, i) + break + } + } + } + c <- true + }(p) + } + <-c + <-c +} + +func TestStoreLoadRelAcq64(t *testutil.TestRunner) { + if runtime.NumCPU() == 1 { + t.Skipf("Skipping test on %v processor machine", runtime.NumCPU()) + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + N := int64(1e3) + if short { + N = int64(1e2) + } + c := make(chan bool, 2) + type Data struct { + signal int64 + pad1 [128]int8 + data1 int64 + pad2 [128]int8 + data2 float64 + } + var X Data + for p := int64(0); p < 2; p++ { + go func(p int64) { + for i := int64(1); i < N; i++ { + if (i+p)%2 == 0 { + X.data1 = i + X.data2 = float64(i) + StoreInt64(&X.signal, i) + } else { + for w := 1; LoadInt64(&X.signal) != i; w++ { + if w%1000 == 0 { + runtime.Gosched() + } + } + d1 := X.data1 + d2 := X.data2 + if d1 != i || d2 != float64(i) { + t.Errorf("incorrect data: %d/%g (%d)", d1, d2, i) + break + } + } + } + c <- true + }(p) + } + <-c + <-c +} + +func shouldPanic(t testing.TB, name string, f func()) { + defer func() { + // Check that all GC maps are sane. + runtime.GC() + + err := recover() + want := "unaligned 64-bit atomic operation" + if err == nil { + t.Errorf("%s did not panic", name) + } else if s, _ := err.(string); s != want { + t.Errorf("%s: wanted panic %q, got %q", name, want, err) + } + }() + f() +} + +func TestUnaligned64(t *testutil.TestRunner) { + // Unaligned 64-bit atomics on 32-bit systems are + // a continual source of pain. Test that on 32-bit systems they crash + // instead of failing silently. + if !arch32 { + t.Skip("test only runs on 32-bit systems") + } + + x := make([]uint32, 4) + p := (*uint64)(unsafe.Pointer(&x[1])) // misaligned + + shouldPanic(t, "LoadUint64", func() { LoadUint64(p) }) + shouldPanic(t, "LoadUint64Method", func() { (*Uint64)(unsafe.Pointer(p)).Load() }) + shouldPanic(t, "StoreUint64", func() { StoreUint64(p, 1) }) + shouldPanic(t, "StoreUint64Method", func() { (*Uint64)(unsafe.Pointer(p)).Store(1) }) + shouldPanic(t, "CompareAndSwapUint64", func() { CompareAndSwapUint64(p, 1, 2) }) + shouldPanic(t, "CompareAndSwapUint64Method", func() { (*Uint64)(unsafe.Pointer(p)).CompareAndSwap(1, 2) }) + shouldPanic(t, "AddUint64", func() { AddUint64(p, 3) }) + shouldPanic(t, "AddUint64Method", func() { (*Uint64)(unsafe.Pointer(p)).Add(3) }) +} + +func TestAutoAligned64(t *testutil.TestRunner) { + var signed struct { + _ uint32 + i Int64 + } + if o := reflect.TypeOf(&signed).Elem().Field(1).Offset; o != 8 { + t.Fatalf("Int64 offset = %d, want 8", o) + } + if p := reflect.ValueOf(&signed).Elem().Field(1).Addr().Pointer(); p&7 != 0 { + t.Fatalf("Int64 pointer = %#x, want 8-aligned", p) + } + + var unsigned struct { + _ uint32 + i Uint64 + } + if o := reflect.TypeOf(&unsigned).Elem().Field(1).Offset; o != 8 { + t.Fatalf("Uint64 offset = %d, want 8", o) + } + if p := reflect.ValueOf(&unsigned).Elem().Field(1).Addr().Pointer(); p&7 != 0 { + t.Fatalf("Int64 pointer = %#x, want 8-aligned", p) + } +} + +func TestNilDeref(t *testutil.TestRunner) { + funcs := [...]func(){ + func() { CompareAndSwapInt32(nil, 0, 0) }, + func() { (*Int32)(nil).CompareAndSwap(0, 0) }, + func() { CompareAndSwapInt64(nil, 0, 0) }, + func() { (*Int64)(nil).CompareAndSwap(0, 0) }, + func() { CompareAndSwapUint32(nil, 0, 0) }, + func() { (*Uint32)(nil).CompareAndSwap(0, 0) }, + func() { CompareAndSwapUint64(nil, 0, 0) }, + func() { (*Uint64)(nil).CompareAndSwap(0, 0) }, + func() { CompareAndSwapUintptr(nil, 0, 0) }, + func() { (*Uintptr)(nil).CompareAndSwap(0, 0) }, + func() { CompareAndSwapPointer(nil, nil, nil) }, + func() { (*Pointer[byte])(nil).CompareAndSwap(nil, nil) }, + func() { SwapInt32(nil, 0) }, + func() { (*Int32)(nil).Swap(0) }, + func() { SwapUint32(nil, 0) }, + func() { (*Uint32)(nil).Swap(0) }, + func() { SwapInt64(nil, 0) }, + func() { (*Int64)(nil).Swap(0) }, + func() { SwapUint64(nil, 0) }, + func() { (*Uint64)(nil).Swap(0) }, + func() { SwapUintptr(nil, 0) }, + func() { (*Uintptr)(nil).Swap(0) }, + func() { SwapPointer(nil, nil) }, + func() { (*Pointer[byte])(nil).Swap(nil) }, + func() { AddInt32(nil, 0) }, + func() { (*Int32)(nil).Add(0) }, + func() { AddUint32(nil, 0) }, + func() { (*Uint32)(nil).Add(0) }, + func() { AddInt64(nil, 0) }, + func() { (*Int64)(nil).Add(0) }, + func() { AddUint64(nil, 0) }, + func() { (*Uint64)(nil).Add(0) }, + func() { AddUintptr(nil, 0) }, + func() { (*Uintptr)(nil).Add(0) }, + func() { LoadInt32(nil) }, + func() { (*Int32)(nil).Load() }, + func() { LoadInt64(nil) }, + func() { (*Int64)(nil).Load() }, + func() { LoadUint32(nil) }, + func() { (*Uint32)(nil).Load() }, + func() { LoadUint64(nil) }, + func() { (*Uint64)(nil).Load() }, + func() { LoadUintptr(nil) }, + func() { (*Uintptr)(nil).Load() }, + func() { LoadPointer(nil) }, + func() { (*Pointer[byte])(nil).Load() }, + func() { StoreInt32(nil, 0) }, + func() { (*Int32)(nil).Store(0) }, + func() { StoreInt64(nil, 0) }, + func() { (*Int64)(nil).Store(0) }, + func() { StoreUint32(nil, 0) }, + func() { (*Uint32)(nil).Store(0) }, + func() { StoreUint64(nil, 0) }, + func() { (*Uint64)(nil).Store(0) }, + func() { StoreUintptr(nil, 0) }, + func() { (*Uintptr)(nil).Store(0) }, + func() { StorePointer(nil, nil) }, + func() { (*Pointer[byte])(nil).Store(nil) }, + } + for _, f := range funcs { + func() { + defer func() { + runtime.GC() + recover() + }() + f() + }() + } +} + +// Test that this compiles. +// When atomic.Pointer used _ [0]T, it did not. +type List struct { + Next Pointer[List] +} diff --git a/cannon/testdata/example/mt-atomic/go.mod b/cannon/testdata/example/mt-atomic/go.mod new file mode 100644 index 0000000000000..042386cf702d2 --- /dev/null +++ b/cannon/testdata/example/mt-atomic/go.mod @@ -0,0 +1,8 @@ +module atomic + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/mt-atomic/main.go b/cannon/testdata/example/mt-atomic/main.go new file mode 100644 index 0000000000000..9d683c5bd598d --- /dev/null +++ b/cannon/testdata/example/mt-atomic/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(TestSwapInt32, "TestSwapInt32") + testutil.RunTest(TestSwapInt32Method, "TestSwapInt32Method") + testutil.RunTest(TestSwapUint32, "TestSwapUint32") + testutil.RunTest(TestSwapUint32Method, "TestSwapUint32Method") + testutil.RunTest(TestSwapInt64, "TestSwapInt64") + testutil.RunTest(TestSwapInt64Method, "TestSwapInt64Method") + testutil.RunTest(TestSwapUint64, "TestSwapUint64") + testutil.RunTest(TestSwapUint64Method, "TestSwapUint64Method") + testutil.RunTest(TestSwapUintptr, "TestSwapUintptr") + testutil.RunTest(TestSwapUintptrMethod, "TestSwapUintptrMethod") + testutil.RunTest(TestSwapPointer, "TestSwapPointer") + testutil.RunTest(TestSwapPointerMethod, "TestSwapPointerMethod") + testutil.RunTest(TestAddInt32, "TestAddInt32") + testutil.RunTest(TestAddInt32Method, "TestAddInt32Method") + testutil.RunTest(TestAddUint32, "TestAddUint32") + testutil.RunTest(TestAddUint32Method, "TestAddUint32Method") + testutil.RunTest(TestAddInt64, "TestAddInt64") + testutil.RunTest(TestAddInt64Method, "TestAddInt64Method") + testutil.RunTest(TestAddUint64, "TestAddUint64") + testutil.RunTest(TestAddUint64Method, "TestAddUint64Method") + testutil.RunTest(TestAddUintptr, "TestAddUintptr") + testutil.RunTest(TestAddUintptrMethod, "TestAddUintptrMethod") + testutil.RunTest(TestCompareAndSwapInt32, "TestCompareAndSwapInt32") + testutil.RunTest(TestCompareAndSwapInt32Method, "TestCompareAndSwapInt32Method") + testutil.RunTest(TestCompareAndSwapUint32, "TestCompareAndSwapUint32") + testutil.RunTest(TestCompareAndSwapUint32Method, "TestCompareAndSwapUint32Method") + testutil.RunTest(TestCompareAndSwapInt64, "TestCompareAndSwapInt64") + testutil.RunTest(TestCompareAndSwapInt64Method, "TestCompareAndSwapInt64Method") + testutil.RunTest(TestCompareAndSwapUint64, "TestCompareAndSwapUint64") + testutil.RunTest(TestCompareAndSwapUint64Method, "TestCompareAndSwapUint64Method") + testutil.RunTest(TestCompareAndSwapUintptr, "TestCompareAndSwapUintptr") + testutil.RunTest(TestCompareAndSwapUintptrMethod, "TestCompareAndSwapUintptrMethod") + testutil.RunTest(TestCompareAndSwapPointer, "TestCompareAndSwapPointer") + testutil.RunTest(TestCompareAndSwapPointerMethod, "TestCompareAndSwapPointerMethod") + testutil.RunTest(TestLoadInt32, "TestLoadInt32") + testutil.RunTest(TestLoadInt32Method, "TestLoadInt32Method") + testutil.RunTest(TestLoadUint32, "TestLoadUint32") + testutil.RunTest(TestLoadUint32Method, "TestLoadUint32Method") + testutil.RunTest(TestLoadInt64, "TestLoadInt64") + testutil.RunTest(TestLoadInt64Method, "TestLoadInt64Method") + testutil.RunTest(TestLoadUint64, "TestLoadUint64") + testutil.RunTest(TestLoadUint64Method, "TestLoadUint64Method") + testutil.RunTest(TestLoadUintptr, "TestLoadUintptr") + testutil.RunTest(TestLoadUintptrMethod, "TestLoadUintptrMethod") + testutil.RunTest(TestLoadPointer, "TestLoadPointer") + testutil.RunTest(TestLoadPointerMethod, "TestLoadPointerMethod") + testutil.RunTest(TestStoreInt32, "TestStoreInt32") + testutil.RunTest(TestStoreInt32Method, "TestStoreInt32Method") + testutil.RunTest(TestStoreUint32, "TestStoreUint32") + testutil.RunTest(TestStoreUint32Method, "TestStoreUint32Method") + testutil.RunTest(TestStoreInt64, "TestStoreInt64") + testutil.RunTest(TestStoreInt64Method, "TestStoreInt64Method") + testutil.RunTest(TestStoreUint64, "TestStoreUint64") + testutil.RunTest(TestStoreUint64Method, "TestStoreUint64Method") + testutil.RunTest(TestStoreUintptr, "TestStoreUintptr") + testutil.RunTest(TestStoreUintptrMethod, "TestStoreUintptrMethod") + testutil.RunTest(TestStorePointer, "TestStorePointer") + testutil.RunTest(TestStorePointerMethod, "TestStorePointerMethod") + testutil.RunTest(TestHammer32, "TestHammer32") + testutil.RunTest(TestHammer64, "TestHammer64") + testutil.RunTest(TestAutoAligned64, "TestAutoAligned64") + testutil.RunTest(TestNilDeref, "TestNilDeref") + testutil.RunTest(TestStoreLoadSeqCst32, "TestStoreLoadSeqCst32") + testutil.RunTest(TestStoreLoadSeqCst64, "TestStoreLoadSeqCst64") + testutil.RunTest(TestStoreLoadRelAcq32, "TestStoreLoadRelAcq32") + testutil.RunTest(TestStoreLoadRelAcq64, "TestStoreLoadRelAcq64") + testutil.RunTest(TestUnaligned64, "TestUnaligned64") + testutil.RunTest(TestHammerStoreLoad, "TestHammerStoreLoad") + + fmt.Println("Atomic tests passed") +} diff --git a/cannon/testdata/example/multithreaded/go.mod b/cannon/testdata/example/mt-cond/go.mod similarity index 58% rename from cannon/testdata/example/multithreaded/go.mod rename to cannon/testdata/example/mt-cond/go.mod index e1bdb77a9aff6..d6d1853d5af2b 100644 --- a/cannon/testdata/example/multithreaded/go.mod +++ b/cannon/testdata/example/mt-cond/go.mod @@ -1,4 +1,4 @@ -module multithreaded +module cond go 1.22 diff --git a/cannon/testdata/example/mt-cond/main.go b/cannon/testdata/example/mt-cond/main.go new file mode 100644 index 0000000000000..2b584cec999b3 --- /dev/null +++ b/cannon/testdata/example/mt-cond/main.go @@ -0,0 +1,302 @@ +// Portions of this code are derived from code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/cond_test.go +// +// --- Original License Notice --- +// +// Copyright 2009 The Go Authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "fmt" + "os" + "reflect" + "runtime" + "sync" +) + +func main() { + TestCondSignal() + TestCondSignalGenerations() + TestCondBroadcast() + TestRace() + TestCondSignalStealing() + TestCondCopy() + + fmt.Println("Cond test passed") +} + +func TestCondSignal() { + var m sync.Mutex + c := sync.NewCond(&m) + n := 2 + running := make(chan bool, n) + awake := make(chan bool, n) + for i := 0; i < n; i++ { + go func() { + m.Lock() + running <- true + c.Wait() + awake <- true + m.Unlock() + }() + } + for i := 0; i < n; i++ { + <-running // Wait for everyone to run. + } + for n > 0 { + select { + case <-awake: + _, _ = fmt.Fprintln(os.Stderr, "goroutine not asleep") + os.Exit(1) + default: + } + m.Lock() + c.Signal() + m.Unlock() + <-awake // Will deadlock if no goroutine wakes up + select { + case <-awake: + _, _ = fmt.Fprintln(os.Stderr, "too many goroutines awake") + os.Exit(1) + default: + } + n-- + } + c.Signal() +} + +func TestCondSignalGenerations() { + var m sync.Mutex + c := sync.NewCond(&m) + n := 100 + running := make(chan bool, n) + awake := make(chan int, n) + for i := 0; i < n; i++ { + go func(i int) { + m.Lock() + running <- true + c.Wait() + awake <- i + m.Unlock() + }(i) + if i > 0 { + a := <-awake + if a != i-1 { + _, _ = fmt.Fprintf(os.Stderr, "wrong goroutine woke up: want %d, got %d\n", i-1, a) + os.Exit(1) + } + } + <-running + m.Lock() + c.Signal() + m.Unlock() + } +} + +func TestCondBroadcast() { + var m sync.Mutex + c := sync.NewCond(&m) + n := 5 + running := make(chan int, n) + awake := make(chan int, n) + exit := false + for i := 0; i < n; i++ { + go func(g int) { + m.Lock() + for !exit { + running <- g + c.Wait() + awake <- g + } + m.Unlock() + }(i) + } + for i := 0; i < n; i++ { + for i := 0; i < n; i++ { + <-running // Will deadlock unless n are running. + } + if i == n-1 { + m.Lock() + exit = true + m.Unlock() + } + select { + case <-awake: + _, _ = fmt.Fprintln(os.Stderr, "goroutine not asleep") + os.Exit(1) + default: + } + m.Lock() + c.Broadcast() + m.Unlock() + seen := make([]bool, n) + for i := 0; i < n; i++ { + g := <-awake + if seen[g] { + _, _ = fmt.Fprintln(os.Stderr, "goroutine woke up twice") + os.Exit(1) + } + seen[g] = true + } + } + select { + case <-running: + _, _ = fmt.Fprintln(os.Stderr, "goroutine still running") + os.Exit(1) + default: + } + c.Broadcast() +} + +func TestRace() { + x := 0 + c := sync.NewCond(&sync.Mutex{}) + done := make(chan bool) + go func() { + c.L.Lock() + x = 1 + c.Wait() + if x != 2 { + _, _ = fmt.Fprintln(os.Stderr, "want 2") + os.Exit(1) + } + x = 3 + c.Signal() + c.L.Unlock() + done <- true + }() + go func() { + c.L.Lock() + for { + if x == 1 { + x = 2 + c.Signal() + break + } + c.L.Unlock() + runtime.Gosched() + c.L.Lock() + } + c.L.Unlock() + done <- true + }() + go func() { + c.L.Lock() + for { + if x == 2 { + c.Wait() + if x != 3 { + _, _ = fmt.Fprintln(os.Stderr, "want 3") + os.Exit(1) + } + break + } + if x == 3 { + break + } + c.L.Unlock() + runtime.Gosched() + c.L.Lock() + } + c.L.Unlock() + done <- true + }() + <-done + <-done + <-done +} + +func TestCondSignalStealing() { + for iters := 0; iters < 5; iters++ { + var m sync.Mutex + cond := sync.NewCond(&m) + + // Start a waiter. + ch := make(chan struct{}) + go func() { + m.Lock() + ch <- struct{}{} + cond.Wait() + m.Unlock() + + ch <- struct{}{} + }() + + <-ch + m.Lock() + m.Unlock() + + // We know that the waiter is in the cond.Wait() call because we + // synchronized with it, then acquired/released the mutex it was + // holding when we synchronized. + // + // Start two goroutines that will race: one will broadcast on + // the cond var, the other will wait on it. + // + // The new waiter may or may not get notified, but the first one + // has to be notified. + done := false + go func() { + cond.Broadcast() + }() + + go func() { + m.Lock() + for !done { + cond.Wait() + } + m.Unlock() + }() + + // Check that the first waiter does get signaled. + <-ch + + // Release the second waiter in case it didn't get the + // broadcast. + m.Lock() + done = true + m.Unlock() + cond.Broadcast() + } +} + +func TestCondCopy() { + defer func() { + err := recover() + if err == nil || err.(string) != "sync.Cond is copied" { + _, _ = fmt.Fprintf(os.Stderr, "got %v, expect sync.Cond is copied", err) + os.Exit(1) + } + }() + c := sync.Cond{L: &sync.Mutex{}} + c.Signal() + var c2 sync.Cond + reflect.ValueOf(&c2).Elem().Set(reflect.ValueOf(&c).Elem()) // c2 := c, hidden from vet + c2.Signal() +} diff --git a/cannon/testdata/example/mt-general/go.mod b/cannon/testdata/example/mt-general/go.mod new file mode 100644 index 0000000000000..3a7bf3680f5f6 --- /dev/null +++ b/cannon/testdata/example/mt-general/go.mod @@ -0,0 +1,5 @@ +module mtgeneral + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/multithreaded/main.go b/cannon/testdata/example/mt-general/main.go similarity index 100% rename from cannon/testdata/example/multithreaded/main.go rename to cannon/testdata/example/mt-general/main.go diff --git a/cannon/testdata/example/mt-map/go.mod b/cannon/testdata/example/mt-map/go.mod new file mode 100644 index 0000000000000..7290b372361e5 --- /dev/null +++ b/cannon/testdata/example/mt-map/go.mod @@ -0,0 +1,8 @@ +module map + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/mt-map/main.go b/cannon/testdata/example/mt-map/main.go new file mode 100644 index 0000000000000..577a19a3ed8aa --- /dev/null +++ b/cannon/testdata/example/mt-map/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(TestMapMatchesRWMutex, "TestMapMatchesRWMutex") + testutil.RunTest(TestMapMatchesDeepCopy, "TestMapMatchesDeepCopy") + testutil.RunTest(TestConcurrentRange, "TestConcurrentRange") + testutil.RunTest(TestIssue40999, "TestIssue40999") + testutil.RunTest(TestMapRangeNestedCall, "TestMapRangeNestedCall") + testutil.RunTest(TestCompareAndSwap_NonExistingKey, "TestCompareAndSwap_NonExistingKey") + testutil.RunTest(TestMapRangeNoAllocations, "TestMapRangeNoAllocations") + + fmt.Println("Map test passed") +} diff --git a/cannon/testdata/example/mt-map/map_reference_test_copy.go b/cannon/testdata/example/mt-map/map_reference_test_copy.go new file mode 100644 index 0000000000000..3beeb1501c556 --- /dev/null +++ b/cannon/testdata/example/mt-map/map_reference_test_copy.go @@ -0,0 +1,299 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/map_reference_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "sync" + "sync/atomic" +) + +// This file contains reference map implementations for unit-tests. + +// mapInterface is the interface Map implements. +type mapInterface interface { + Load(any) (any, bool) + Store(key, value any) + LoadOrStore(key, value any) (actual any, loaded bool) + LoadAndDelete(key any) (value any, loaded bool) + Delete(any) + Swap(key, value any) (previous any, loaded bool) + CompareAndSwap(key, old, new any) (swapped bool) + CompareAndDelete(key, old any) (deleted bool) + Range(func(key, value any) (shouldContinue bool)) +} + +var ( + _ mapInterface = &RWMutexMap{} + _ mapInterface = &DeepCopyMap{} +) + +// RWMutexMap is an implementation of mapInterface using a sync.RWMutex. +type RWMutexMap struct { + mu sync.RWMutex + dirty map[any]any +} + +func (m *RWMutexMap) Load(key any) (value any, ok bool) { + m.mu.RLock() + value, ok = m.dirty[key] + m.mu.RUnlock() + return +} + +func (m *RWMutexMap) Store(key, value any) { + m.mu.Lock() + if m.dirty == nil { + m.dirty = make(map[any]any) + } + m.dirty[key] = value + m.mu.Unlock() +} + +func (m *RWMutexMap) LoadOrStore(key, value any) (actual any, loaded bool) { + m.mu.Lock() + actual, loaded = m.dirty[key] + if !loaded { + actual = value + if m.dirty == nil { + m.dirty = make(map[any]any) + } + m.dirty[key] = value + } + m.mu.Unlock() + return actual, loaded +} + +func (m *RWMutexMap) Swap(key, value any) (previous any, loaded bool) { + m.mu.Lock() + if m.dirty == nil { + m.dirty = make(map[any]any) + } + + previous, loaded = m.dirty[key] + m.dirty[key] = value + m.mu.Unlock() + return +} + +func (m *RWMutexMap) LoadAndDelete(key any) (value any, loaded bool) { + m.mu.Lock() + value, loaded = m.dirty[key] + if !loaded { + m.mu.Unlock() + return nil, false + } + delete(m.dirty, key) + m.mu.Unlock() + return value, loaded +} + +func (m *RWMutexMap) Delete(key any) { + m.mu.Lock() + delete(m.dirty, key) + m.mu.Unlock() +} + +func (m *RWMutexMap) CompareAndSwap(key, old, new any) (swapped bool) { + m.mu.Lock() + defer m.mu.Unlock() + if m.dirty == nil { + return false + } + + value, loaded := m.dirty[key] + if loaded && value == old { + m.dirty[key] = new + return true + } + return false +} + +func (m *RWMutexMap) CompareAndDelete(key, old any) (deleted bool) { + m.mu.Lock() + defer m.mu.Unlock() + if m.dirty == nil { + return false + } + + value, loaded := m.dirty[key] + if loaded && value == old { + delete(m.dirty, key) + return true + } + return false +} + +func (m *RWMutexMap) Range(f func(key, value any) (shouldContinue bool)) { + m.mu.RLock() + keys := make([]any, 0, len(m.dirty)) + for k := range m.dirty { + keys = append(keys, k) + } + m.mu.RUnlock() + + for _, k := range keys { + v, ok := m.Load(k) + if !ok { + continue + } + if !f(k, v) { + break + } + } +} + +// DeepCopyMap is an implementation of mapInterface using a Mutex and +// atomic.Value. It makes deep copies of the map on every write to avoid +// acquiring the Mutex in Load. +type DeepCopyMap struct { + mu sync.Mutex + clean atomic.Value +} + +func (m *DeepCopyMap) Load(key any) (value any, ok bool) { + clean, _ := m.clean.Load().(map[any]any) + value, ok = clean[key] + return value, ok +} + +func (m *DeepCopyMap) Store(key, value any) { + m.mu.Lock() + dirty := m.dirty() + dirty[key] = value + m.clean.Store(dirty) + m.mu.Unlock() +} + +func (m *DeepCopyMap) LoadOrStore(key, value any) (actual any, loaded bool) { + clean, _ := m.clean.Load().(map[any]any) + actual, loaded = clean[key] + if loaded { + return actual, loaded + } + + m.mu.Lock() + // Reload clean in case it changed while we were waiting on m.mu. + clean, _ = m.clean.Load().(map[any]any) + actual, loaded = clean[key] + if !loaded { + dirty := m.dirty() + dirty[key] = value + actual = value + m.clean.Store(dirty) + } + m.mu.Unlock() + return actual, loaded +} + +func (m *DeepCopyMap) Swap(key, value any) (previous any, loaded bool) { + m.mu.Lock() + dirty := m.dirty() + previous, loaded = dirty[key] + dirty[key] = value + m.clean.Store(dirty) + m.mu.Unlock() + return +} + +func (m *DeepCopyMap) LoadAndDelete(key any) (value any, loaded bool) { + m.mu.Lock() + dirty := m.dirty() + value, loaded = dirty[key] + delete(dirty, key) + m.clean.Store(dirty) + m.mu.Unlock() + return +} + +func (m *DeepCopyMap) Delete(key any) { + m.mu.Lock() + dirty := m.dirty() + delete(dirty, key) + m.clean.Store(dirty) + m.mu.Unlock() +} + +func (m *DeepCopyMap) CompareAndSwap(key, old, new any) (swapped bool) { + clean, _ := m.clean.Load().(map[any]any) + if previous, ok := clean[key]; !ok || previous != old { + return false + } + + m.mu.Lock() + defer m.mu.Unlock() + dirty := m.dirty() + value, loaded := dirty[key] + if loaded && value == old { + dirty[key] = new + m.clean.Store(dirty) + return true + } + return false +} + +func (m *DeepCopyMap) CompareAndDelete(key, old any) (deleted bool) { + clean, _ := m.clean.Load().(map[any]any) + if previous, ok := clean[key]; !ok || previous != old { + return false + } + + m.mu.Lock() + defer m.mu.Unlock() + + dirty := m.dirty() + value, loaded := dirty[key] + if loaded && value == old { + delete(dirty, key) + m.clean.Store(dirty) + return true + } + return false +} + +func (m *DeepCopyMap) Range(f func(key, value any) (shouldContinue bool)) { + clean, _ := m.clean.Load().(map[any]any) + for k, v := range clean { + if !f(k, v) { + break + } + } +} + +func (m *DeepCopyMap) dirty() map[any]any { + clean, _ := m.clean.Load().(map[any]any) + dirty := make(map[any]any, len(clean)+1) + for k, v := range clean { + dirty[k] = v + } + return dirty +} diff --git a/cannon/testdata/example/mt-map/map_test_copy.go b/cannon/testdata/example/mt-map/map_test_copy.go new file mode 100644 index 0000000000000..7b8806698f9fc --- /dev/null +++ b/cannon/testdata/example/mt-map/map_test_copy.go @@ -0,0 +1,325 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/map_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "math/rand" + "reflect" + "runtime" + "sync" + "sync/atomic" + "testing" + "testing/quick" + + "utils/testutil" +) + +type mapOp string + +const ( + opLoad = mapOp("Load") + opStore = mapOp("Store") + opLoadOrStore = mapOp("LoadOrStore") + opLoadAndDelete = mapOp("LoadAndDelete") + opDelete = mapOp("Delete") + opSwap = mapOp("Swap") + opCompareAndSwap = mapOp("CompareAndSwap") + opCompareAndDelete = mapOp("CompareAndDelete") +) + +var mapOps = [...]mapOp{ + opLoad, + opStore, + opLoadOrStore, + opLoadAndDelete, + opDelete, + opSwap, + opCompareAndSwap, + opCompareAndDelete, +} + +// mapCall is a quick.Generator for calls on mapInterface. +type mapCall struct { + op mapOp + k, v any +} + +func (c mapCall) apply(m mapInterface) (any, bool) { + switch c.op { + case opLoad: + return m.Load(c.k) + case opStore: + m.Store(c.k, c.v) + return nil, false + case opLoadOrStore: + return m.LoadOrStore(c.k, c.v) + case opLoadAndDelete: + return m.LoadAndDelete(c.k) + case opDelete: + m.Delete(c.k) + return nil, false + case opSwap: + return m.Swap(c.k, c.v) + case opCompareAndSwap: + if m.CompareAndSwap(c.k, c.v, rand.Int()) { + m.Delete(c.k) + return c.v, true + } + return nil, false + case opCompareAndDelete: + if m.CompareAndDelete(c.k, c.v) { + if _, ok := m.Load(c.k); !ok { + return nil, true + } + } + return nil, false + default: + panic("invalid mapOp") + } +} + +type mapResult struct { + value any + ok bool +} + +func randValue(r *rand.Rand) any { + b := make([]byte, r.Intn(4)) + for i := range b { + b[i] = 'a' + byte(rand.Intn(26)) + } + return string(b) +} + +func (mapCall) Generate(r *rand.Rand, size int) reflect.Value { + c := mapCall{op: mapOps[rand.Intn(len(mapOps))], k: randValue(r)} + switch c.op { + case opStore, opLoadOrStore: + c.v = randValue(r) + } + return reflect.ValueOf(c) +} + +func applyCalls(m mapInterface, calls []mapCall) (results []mapResult, final map[any]any) { + for _, c := range calls { + v, ok := c.apply(m) + results = append(results, mapResult{v, ok}) + } + + final = make(map[any]any) + m.Range(func(k, v any) bool { + final[k] = v + return true + }) + + return results, final +} + +func applyMap(calls []mapCall) ([]mapResult, map[any]any) { + return applyCalls(new(sync.Map), calls) +} + +func applyRWMutexMap(calls []mapCall) ([]mapResult, map[any]any) { + return applyCalls(new(RWMutexMap), calls) +} + +func applyDeepCopyMap(calls []mapCall) ([]mapResult, map[any]any) { + return applyCalls(new(DeepCopyMap), calls) +} + +func TestMapMatchesRWMutex(t *testutil.TestRunner) { + if err := quick.CheckEqual(applyMap, applyRWMutexMap, nil); err != nil { + t.Error(err) + } +} + +func TestMapMatchesDeepCopy(t *testutil.TestRunner) { + if err := quick.CheckEqual(applyMap, applyDeepCopyMap, nil); err != nil { + t.Error(err) + } +} + +func TestConcurrentRange(t *testutil.TestRunner) { + const mapSize = 1 << 10 + + m := new(sync.Map) + for n := int64(1); n <= mapSize; n++ { + m.Store(n, int64(n)) + } + + done := make(chan struct{}) + var wg sync.WaitGroup + defer func() { + close(done) + wg.Wait() + }() + for g := int64(runtime.GOMAXPROCS(0)); g > 0; g-- { + r := rand.New(rand.NewSource(g)) + wg.Add(1) + go func(g int64) { + defer wg.Done() + for i := int64(0); ; i++ { + select { + case <-done: + return + default: + } + for n := int64(1); n < mapSize; n++ { + if r.Int63n(mapSize) == 0 { + m.Store(n, n*i*g) + } else { + m.Load(n) + } + } + } + }(g) + } + + //iters := 1 << 10 + //if testing.Short() { + // iters = 16 + //} + iters := 16 + for n := iters; n > 0; n-- { + seen := make(map[int64]bool, mapSize) + + m.Range(func(ki, vi any) bool { + k, v := ki.(int64), vi.(int64) + if v%k != 0 { + t.Fatalf("while Storing multiples of %v, Range saw value %v", k, v) + } + if seen[k] { + t.Fatalf("Range visited key %v twice", k) + } + seen[k] = true + return true + }) + + if len(seen) != mapSize { + t.Fatalf("Range visited %v elements of %v-element Map", len(seen), mapSize) + } + } +} + +func TestIssue40999(t *testutil.TestRunner) { + var m sync.Map + + // Since the miss-counting in missLocked (via Delete) + // compares the miss count with len(m.dirty), + // add an initial entry to bias len(m.dirty) above the miss count. + m.Store(nil, struct{}{}) + + var finalized uint32 + + // Set finalizers that count for collected keys. A non-zero count + // indicates that keys have not been leaked. + for atomic.LoadUint32(&finalized) == 0 { + p := new(int) + runtime.SetFinalizer(p, func(*int) { + atomic.AddUint32(&finalized, 1) + }) + m.Store(p, struct{}{}) + m.Delete(p) + runtime.GC() + } +} + +func TestMapRangeNestedCall(t *testutil.TestRunner) { // Issue 46399 + var m sync.Map + for i, v := range [3]string{"hello", "world", "Go"} { + m.Store(i, v) + } + m.Range(func(key, value any) bool { + m.Range(func(key, value any) bool { + // We should be able to load the key offered in the Range callback, + // because there are no concurrent Delete involved in this tested map. + if v, ok := m.Load(key); !ok || !reflect.DeepEqual(v, value) { + t.Fatalf("Nested Range loads unexpected value, got %+v want %+v", v, value) + } + + // We didn't keep 42 and a value into the map before, if somehow we loaded + // a value from such a key, meaning there must be an internal bug regarding + // nested range in the Map. + if _, loaded := m.LoadOrStore(42, "dummy"); loaded { + t.Fatalf("Nested Range loads unexpected value, want store a new value") + } + + // Try to Store then LoadAndDelete the corresponding value with the key + // 42 to the Map. In this case, the key 42 and associated value should be + // removed from the Map. Therefore any future range won't observe key 42 + // as we checked in above. + val := "sync.Map" + m.Store(42, val) + if v, loaded := m.LoadAndDelete(42); !loaded || !reflect.DeepEqual(v, val) { + t.Fatalf("Nested Range loads unexpected value, got %v, want %v", v, val) + } + return true + }) + + // Remove key from Map on-the-fly. + m.Delete(key) + return true + }) + + // After a Range of Delete, all keys should be removed and any + // further Range won't invoke the callback. Hence length remains 0. + length := 0 + m.Range(func(key, value any) bool { + length++ + return true + }) + + if length != 0 { + t.Fatalf("Unexpected sync.Map size, got %v want %v", length, 0) + } +} + +func TestCompareAndSwap_NonExistingKey(t *testutil.TestRunner) { + m := &sync.Map{} + if m.CompareAndSwap(m, nil, 42) { + // See https://go.dev/issue/51972#issuecomment-1126408637. + t.Fatalf("CompareAndSwap on a non-existing key succeeded") + } +} + +func TestMapRangeNoAllocations(t *testutil.TestRunner) { // Issue 62404 + var m sync.Map + allocs := testing.AllocsPerRun(10, func() { + m.Range(func(key, value any) bool { + return true + }) + }) + if allocs > 0 { + t.Errorf("AllocsPerRun of m.Range = %v; want 0", allocs) + } +} diff --git a/cannon/testdata/example/mt-mutex/go.mod b/cannon/testdata/example/mt-mutex/go.mod new file mode 100644 index 0000000000000..3aceb4c8cebfa --- /dev/null +++ b/cannon/testdata/example/mt-mutex/go.mod @@ -0,0 +1,8 @@ +module mutex + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/mt-mutex/main.go b/cannon/testdata/example/mt-mutex/main.go new file mode 100644 index 0000000000000..1a3b75c231bc9 --- /dev/null +++ b/cannon/testdata/example/mt-mutex/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(TestSemaphore, "TestSemaphore") + testutil.RunTest(TestMutex, "TestMutex") + testutil.RunTest(TestMutexFairness, "TestMutexFairness") + + fmt.Println("Mutex test passed") +} diff --git a/cannon/testdata/example/mt-mutex/mutex_test_copy.go b/cannon/testdata/example/mt-mutex/mutex_test_copy.go new file mode 100644 index 0000000000000..d3ed9343c3d4b --- /dev/null +++ b/cannon/testdata/example/mt-mutex/mutex_test_copy.go @@ -0,0 +1,135 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/mutex_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "runtime" + . "sync" + "time" + + "utils/testutil" +) + +func HammerSemaphore(s *uint32, loops int, cdone chan bool) { + for i := 0; i < loops; i++ { + Runtime_Semacquire(s) + Runtime_Semrelease(s, false, 0) + } + cdone <- true +} + +func TestSemaphore(t *testutil.TestRunner) { + s := new(uint32) + *s = 1 + c := make(chan bool) + for i := 0; i < 10; i++ { + go HammerSemaphore(s, 1000, c) + } + for i := 0; i < 10; i++ { + <-c + } +} + +func HammerMutex(m *Mutex, loops int, cdone chan bool) { + for i := 0; i < loops; i++ { + if i%3 == 0 { + if m.TryLock() { + m.Unlock() + } + continue + } + m.Lock() + m.Unlock() + } + cdone <- true +} + +func TestMutex(t *testutil.TestRunner) { + if n := runtime.SetMutexProfileFraction(1); n != 0 { + t.Logf("got mutexrate %d expected 0", n) + } + defer runtime.SetMutexProfileFraction(0) + + m := new(Mutex) + + m.Lock() + if m.TryLock() { + t.Fatalf("TryLock succeeded with mutex locked") + } + m.Unlock() + if !m.TryLock() { + t.Fatalf("TryLock failed with mutex unlocked") + } + m.Unlock() + + c := make(chan bool) + for i := 0; i < 10; i++ { + go HammerMutex(m, 1000, c) + } + for i := 0; i < 10; i++ { + <-c + } +} + +func TestMutexFairness(t *testutil.TestRunner) { + var mu Mutex + stop := make(chan bool) + defer close(stop) + go func() { + for { + mu.Lock() + time.Sleep(100 * time.Microsecond) + mu.Unlock() + select { + case <-stop: + return + default: + } + } + }() + done := make(chan bool, 1) + go func() { + for i := 0; i < 10; i++ { + time.Sleep(100 * time.Microsecond) + mu.Lock() + mu.Unlock() + } + done <- true + }() + select { + case <-done: + case <-time.After(10 * time.Second): + t.Fatalf("can't acquire Mutex in 10 seconds") + } +} diff --git a/cannon/testdata/example/mt-mutex/runtime.go b/cannon/testdata/example/mt-mutex/runtime.go new file mode 100644 index 0000000000000..8d70cb10b354b --- /dev/null +++ b/cannon/testdata/example/mt-mutex/runtime.go @@ -0,0 +1,14 @@ +package main + +import ( + _ "unsafe" // Required for go:linkname +) + +var Runtime_Semacquire = runtime_Semacquire +var Runtime_Semrelease = runtime_Semrelease + +//go:linkname runtime_Semacquire sync.runtime_Semacquire +func runtime_Semacquire(s *uint32) + +//go:linkname runtime_Semrelease sync.runtime_Semrelease +func runtime_Semrelease(s *uint32, handoff bool, skipframes int) diff --git a/cannon/testdata/example/mt-once/go.mod b/cannon/testdata/example/mt-once/go.mod new file mode 100644 index 0000000000000..7595e1de483fa --- /dev/null +++ b/cannon/testdata/example/mt-once/go.mod @@ -0,0 +1,5 @@ +module once + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/mt-once/main.go b/cannon/testdata/example/mt-once/main.go new file mode 100644 index 0000000000000..3be753e2f702e --- /dev/null +++ b/cannon/testdata/example/mt-once/main.go @@ -0,0 +1,98 @@ +// Portions of this code are derived from code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/once_test.go +// +// --- Original License Notice --- +// +// Copyright 2009 The Go Authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "fmt" + "os" + "sync" +) + +func main() { + TestOnce() + TestOncePanic() + + fmt.Println("Once test passed") +} + +type one int + +func (o *one) Increment() { + *o++ +} + +func run(once *sync.Once, o *one, c chan bool) { + once.Do(func() { o.Increment() }) + if v := *o; v != 1 { + _, _ = fmt.Fprintf(os.Stderr, "once failed inside run: %d is not 1\n", v) + os.Exit(1) + } + c <- true +} + +func TestOnce() { + o := new(one) + once := new(sync.Once) + c := make(chan bool) + const N = 10 + for i := 0; i < N; i++ { + go run(once, o, c) + } + for i := 0; i < N; i++ { + <-c + } + if *o != 1 { + _, _ = fmt.Fprintf(os.Stderr, "once failed outside run: %d is not 1\n", *o) + os.Exit(1) + } +} + +func TestOncePanic() { + var once sync.Once + func() { + defer func() { + if r := recover(); r == nil { + _, _ = fmt.Fprintf(os.Stderr, "Once.Do did not panic") + os.Exit(1) + } + }() + once.Do(func() { + panic("failed") + }) + }() + + once.Do(func() { + _, _ = fmt.Fprintf(os.Stderr, "Once.Do called twice") + os.Exit(1) + }) +} diff --git a/cannon/testdata/example/mt-oncefunc/go.mod b/cannon/testdata/example/mt-oncefunc/go.mod new file mode 100644 index 0000000000000..e0f45e8c87904 --- /dev/null +++ b/cannon/testdata/example/mt-oncefunc/go.mod @@ -0,0 +1,8 @@ +module oncefunc + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/mt-oncefunc/main.go b/cannon/testdata/example/mt-oncefunc/main.go new file mode 100644 index 0000000000000..d5b0badc292a0 --- /dev/null +++ b/cannon/testdata/example/mt-oncefunc/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(TestOnceFunc, "TestOnceFunc") + testutil.RunTest(TestOnceValue, "TestOnceValue") + testutil.RunTest(TestOnceValues, "TestOnceValues") + testutil.RunTest(TestOnceFuncPanic, "TestOnceFuncPanic") + testutil.RunTest(TestOnceValuePanic, "TestOnceValuePanic") + testutil.RunTest(TestOnceValuesPanic, "TestOnceValuesPanic") + testutil.RunTest(TestOnceFuncPanicNil, "TestOnceFuncPanicNil") + testutil.RunTest(TestOnceFuncGoexit, "TestOnceFuncGoexit") + testutil.RunTest(TestOnceFuncPanicTraceback, "TestOnceFuncPanicTraceback") + testutil.RunTest(TestOnceXGC, "TestOnceXGC") + + fmt.Println("OnceFunc tests passed") +} diff --git a/cannon/testdata/example/mt-oncefunc/oncefunc_test_copy.go b/cannon/testdata/example/mt-oncefunc/oncefunc_test_copy.go new file mode 100644 index 0000000000000..fdbe93c260aa1 --- /dev/null +++ b/cannon/testdata/example/mt-oncefunc/oncefunc_test_copy.go @@ -0,0 +1,265 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/oncefunc_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "bytes" + "math" + "runtime" + "runtime/debug" + "sync" + "sync/atomic" + "testing" + _ "unsafe" + + "utils/testutil" +) + +// We assume that the Once.Do tests have already covered parallelism. + +func TestOnceFunc(t *testutil.TestRunner) { + calls := 0 + f := sync.OnceFunc(func() { calls++ }) + allocs := testing.AllocsPerRun(10, f) + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +func TestOnceValue(t *testutil.TestRunner) { + calls := 0 + f := sync.OnceValue(func() int { + calls++ + return calls + }) + allocs := testing.AllocsPerRun(10, func() { f() }) + value := f() + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if value != 1 { + t.Errorf("want value==1, got %d", value) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +func TestOnceValues(t *testutil.TestRunner) { + calls := 0 + f := sync.OnceValues(func() (int, int) { + calls++ + return calls, calls + 1 + }) + allocs := testing.AllocsPerRun(10, func() { f() }) + v1, v2 := f() + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if v1 != 1 || v2 != 2 { + t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +func testOncePanicX(t testing.TB, calls *int, f func()) { + testOncePanicWith(t, calls, f, func(label string, p any) { + if p != "x" { + t.Fatalf("%s: want panic %v, got %v", label, "x", p) + } + }) +} + +func testOncePanicWith(t testing.TB, calls *int, f func(), check func(label string, p any)) { + // Check that the each call to f panics with the same value, but the + // underlying function is only called once. + for _, label := range []string{"first time", "second time"} { + var p any + panicked := true + func() { + defer func() { + p = recover() + }() + f() + panicked = false + }() + if !panicked { + t.Fatalf("%s: f did not panic", label) + } + check(label, p) + } + if *calls != 1 { + t.Errorf("want calls==1, got %d", *calls) + } +} + +func TestOnceFuncPanic(t *testutil.TestRunner) { + calls := 0 + f := sync.OnceFunc(func() { + calls++ + panic("x") + }) + testOncePanicX(t, &calls, f) +} + +func TestOnceValuePanic(t *testutil.TestRunner) { + calls := 0 + f := sync.OnceValue(func() int { + calls++ + panic("x") + }) + testOncePanicX(t, &calls, func() { f() }) +} + +func TestOnceValuesPanic(t *testutil.TestRunner) { + calls := 0 + f := sync.OnceValues(func() (int, int) { + calls++ + panic("x") + }) + testOncePanicX(t, &calls, func() { f() }) +} + +func TestOnceFuncPanicNil(t *testutil.TestRunner) { + calls := 0 + f := sync.OnceFunc(func() { + calls++ + panic(nil) + }) + testOncePanicWith(t, &calls, f, func(label string, p any) { + switch p.(type) { + case nil, *runtime.PanicNilError: + return + } + t.Fatalf("%s: want nil panic, got %v", label, p) + }) +} + +func TestOnceFuncGoexit(t *testutil.TestRunner) { + // If f calls Goexit, the results are unspecified. But check that f doesn't + // get called twice. + calls := 0 + f := sync.OnceFunc(func() { + calls++ + runtime.Goexit() + }) + var wg sync.WaitGroup + for i := 0; i < 2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + defer func() { recover() }() + f() + }() + wg.Wait() + } + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } +} + +func TestOnceFuncPanicTraceback(t *testutil.TestRunner) { + // Test that on the first invocation of a OnceFunc, the stack trace goes all + // the way to the origin of the panic. + f := sync.OnceFunc(onceFuncPanic) + + defer func() { + if p := recover(); p != "x" { + t.Fatalf("want panic %v, got %v", "x", p) + } + stack := debug.Stack() + //want := "sync_test.onceFuncPanic" + want := "main.onceFuncPanic" + if !bytes.Contains(stack, []byte(want)) { + t.Fatalf("want stack containing %v, got:\n%s", want, string(stack)) + } + }() + f() +} + +func onceFuncPanic() { + panic("x") +} + +func TestOnceXGC(t *testutil.TestRunner) { + fns := map[string]func([]byte) func(){ + "OnceFunc": func(buf []byte) func() { + return sync.OnceFunc(func() { buf[0] = 1 }) + }, + "OnceValue": func(buf []byte) func() { + f := sync.OnceValue(func() any { buf[0] = 1; return nil }) + return func() { f() } + }, + "OnceValues": func(buf []byte) func() { + f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil }) + return func() { f() } + }, + } + for n, fn := range fns { + t.Run(n, func(t testing.TB) { + buf := make([]byte, 1024) + var gc atomic.Bool + runtime.SetFinalizer(&buf[0], func(_ *byte) { + gc.Store(true) + }) + f := fn(buf) + gcwaitfin() + if gc.Load() != false { + t.Fatal("wrapped function garbage collected too early") + } + f() + gcwaitfin() + if gc.Load() != true { + // Even if f is still alive, the function passed to Once(Func|Value|Values) + // is not kept alive after the first call to f. + t.Fatal("wrapped function should be garbage collected, but still live") + } + f() + }) + } +} + +// gcwaitfin performs garbage collection and waits for all finalizers to run. +func gcwaitfin() { + runtime.GC() + runtime_blockUntilEmptyFinalizerQueue(math.MaxInt64) +} + +//go:linkname runtime_blockUntilEmptyFinalizerQueue runtime.blockUntilEmptyFinalizerQueue +func runtime_blockUntilEmptyFinalizerQueue(int64) bool diff --git a/cannon/testdata/example/mt-pool/export_test_copy.go b/cannon/testdata/example/mt-pool/export_test_copy.go new file mode 100644 index 0000000000000..37b7c4a92bf55 --- /dev/null +++ b/cannon/testdata/example/mt-pool/export_test_copy.go @@ -0,0 +1,86 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/export_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +// Export for testing. +// var Runtime_Semacquire = runtime_Semacquire +// var Runtime_Semrelease = runtime_Semrelease +var Runtime_procPin = runtime_procPin +var Runtime_procUnpin = runtime_procUnpin + +// poolDequeue testing. +type PoolDequeue interface { + PushHead(val any) bool + PopHead() (any, bool) + PopTail() (any, bool) +} + +func NewPoolDequeue(n int) PoolDequeue { + d := &poolDequeue{ + vals: make([]eface, n), + } + // For testing purposes, set the head and tail indexes close + // to wrapping around. + d.headTail.Store(d.pack(1< ../../utils diff --git a/cannon/testdata/example/mt-pool/main.go b/cannon/testdata/example/mt-pool/main.go new file mode 100644 index 0000000000000..2c138b07cd976 --- /dev/null +++ b/cannon/testdata/example/mt-pool/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(TestPool, "TestPool") + testutil.RunTest(TestPoolNew, "TestPoolNew") + testutil.RunTest(TestPoolGC, "TestPoolGC") + testutil.RunTest(TestPoolRelease, "TestPoolRelease") + testutil.RunTest(TestPoolStress, "TestPoolStress") + testutil.RunTest(TestPoolDequeue, "TestPoolDequeue") + testutil.RunTest(TestPoolChain, "TestPoolChain") + testutil.RunTest(TestNilPool, "TestNilPool") + + fmt.Println("Pool test passed") +} diff --git a/cannon/testdata/example/mt-pool/pool_test_copy.go b/cannon/testdata/example/mt-pool/pool_test_copy.go new file mode 100644 index 0000000000000..962cbfce7b5e5 --- /dev/null +++ b/cannon/testdata/example/mt-pool/pool_test_copy.go @@ -0,0 +1,298 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/pool_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "runtime" + "runtime/debug" + . "sync" + "sync/atomic" + "testing" + "time" + + "utils/testutil" +) + +var short bool = true + +func TestPool(t *testutil.TestRunner) { + // disable GC so we can control when it happens. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) + var p Pool + if p.Get() != nil { + t.Fatal("expected empty") + } + + // Make sure that the goroutine doesn't migrate to another P + // between Put and Get calls. + Runtime_procPin() + p.Put("a") + p.Put("b") + if g := p.Get(); g != "a" { + t.Fatalf("got %#v; want a", g) + } + if g := p.Get(); g != "b" { + t.Fatalf("got %#v; want b", g) + } + if g := p.Get(); g != nil { + t.Fatalf("got %#v; want nil", g) + } + Runtime_procUnpin() + + // Put in a large number of objects so they spill into + // stealable space. + for i := 0; i < 100; i++ { + p.Put("c") + } + // After one GC, the victim cache should keep them alive. + runtime.GC() + if g := p.Get(); g != "c" { + t.Fatalf("got %#v; want c after GC", g) + } + // A second GC should drop the victim cache. + runtime.GC() + if g := p.Get(); g != nil { + t.Fatalf("got %#v; want nil after second GC", g) + } +} + +func TestPoolNew(t *testutil.TestRunner) { + // disable GC so we can control when it happens. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) + + i := 0 + p := Pool{ + New: func() any { + i++ + return i + }, + } + if v := p.Get(); v != 1 { + t.Fatalf("got %v; want 1", v) + } + if v := p.Get(); v != 2 { + t.Fatalf("got %v; want 2", v) + } + + // Make sure that the goroutine doesn't migrate to another P + // between Put and Get calls. + Runtime_procPin() + p.Put(42) + if v := p.Get(); v != 42 { + t.Fatalf("got %v; want 42", v) + } + Runtime_procUnpin() + + if v := p.Get(); v != 3 { + t.Fatalf("got %v; want 3", v) + } +} + +// Test that Pool does not hold pointers to previously cached resources. +func TestPoolGC(t *testutil.TestRunner) { + testPool(t, true) +} + +// Test that Pool releases resources on GC. +func TestPoolRelease(t *testutil.TestRunner) { + testPool(t, false) +} + +func testPool(t testing.TB, drain bool) { + var p Pool + const N = 100 +loop: + for try := 0; try < 3; try++ { + if try == 1 && short { + break + } + var fin, fin1 uint32 + for i := 0; i < N; i++ { + v := new(string) + runtime.SetFinalizer(v, func(vv *string) { + atomic.AddUint32(&fin, 1) + }) + p.Put(v) + } + if drain { + for i := 0; i < N; i++ { + p.Get() + } + } + for i := 0; i < 5; i++ { + runtime.GC() + time.Sleep(time.Duration(i*100+10) * time.Millisecond) + // 1 pointer can remain on stack or elsewhere + if fin1 = atomic.LoadUint32(&fin); fin1 >= N-1 { + continue loop + } + } + t.Fatalf("only %v out of %v resources are finalized on try %v", fin1, N, try) + } +} + +func TestPoolStress(t *testutil.TestRunner) { + const P = 10 + N := int(1e6) + if short { + N /= 100 + } + var p Pool + done := make(chan bool) + for i := 0; i < P; i++ { + go func() { + var v any = 0 + for j := 0; j < N; j++ { + if v == nil { + v = 0 + } + p.Put(v) + v = p.Get() + if v != nil && v.(int) != 0 { + t.Errorf("expect 0, got %v", v) + break + } + } + done <- true + }() + } + for i := 0; i < P; i++ { + <-done + } +} + +func TestPoolDequeue(t *testutil.TestRunner) { + testPoolDequeue(t, NewPoolDequeue(16)) +} + +func TestPoolChain(t *testutil.TestRunner) { + testPoolDequeue(t, NewPoolChain()) +} + +func testPoolDequeue(t testing.TB, d PoolDequeue) { + const P = 10 + var N int = 2e6 + if short { + N = 1e3 + } + have := make([]int32, N) + var stop int32 + var wg WaitGroup + record := func(val int) { + atomic.AddInt32(&have[val], 1) + if val == N-1 { + atomic.StoreInt32(&stop, 1) + } + } + + // Start P-1 consumers. + for i := 1; i < P; i++ { + wg.Add(1) + go func() { + fail := 0 + for atomic.LoadInt32(&stop) == 0 { + val, ok := d.PopTail() + if ok { + fail = 0 + record(val.(int)) + } else { + // Speed up the test by + // allowing the pusher to run. + if fail++; fail%100 == 0 { + runtime.Gosched() + } + } + } + wg.Done() + }() + } + + // Start 1 producer. + nPopHead := 0 + wg.Add(1) + go func() { + for j := 0; j < N; j++ { + for !d.PushHead(j) { + // Allow a popper to run. + runtime.Gosched() + } + if j%10 == 0 { + val, ok := d.PopHead() + if ok { + nPopHead++ + record(val.(int)) + } + } + } + wg.Done() + }() + wg.Wait() + + // Check results. + for i, count := range have { + if count != 1 { + t.Errorf("expected have[%d] = 1, got %d", i, count) + } + } + // Check that at least some PopHeads succeeded. We skip this + // check in short mode because it's common enough that the + // queue will stay nearly empty all the time and a PopTail + // will happen during the window between every PushHead and + // PopHead. + if !short && nPopHead == 0 { + t.Errorf("popHead never succeeded") + } +} + +func TestNilPool(t *testutil.TestRunner) { + catch := func() { + if recover() == nil { + t.Error("expected panic") + } + } + + var p *Pool + t.Run("Get", func(t testing.TB) { + defer catch() + if p.Get() != nil { + t.Error("expected empty") + } + t.Error("should have panicked already") + }) + t.Run("Put", func(t testing.TB) { + defer catch() + p.Put("a") + t.Error("should have panicked already") + }) +} diff --git a/cannon/testdata/example/mt-pool/poolqueue_copy.go b/cannon/testdata/example/mt-pool/poolqueue_copy.go new file mode 100644 index 0000000000000..5aa7b1ffa7b94 --- /dev/null +++ b/cannon/testdata/example/mt-pool/poolqueue_copy.go @@ -0,0 +1,338 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/poolqueue.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "sync/atomic" + "unsafe" +) + +// poolDequeue is a lock-free fixed-size single-producer, +// multi-consumer queue. The single producer can both push and pop +// from the head, and consumers can pop from the tail. +// +// It has the added feature that it nils out unused slots to avoid +// unnecessary retention of objects. This is important for sync.Pool, +// but not typically a property considered in the literature. +type poolDequeue struct { + // headTail packs together a 32-bit head index and a 32-bit + // tail index. Both are indexes into vals modulo len(vals)-1. + // + // tail = index of oldest data in queue + // head = index of next slot to fill + // + // Slots in the range [tail, head) are owned by consumers. + // A consumer continues to own a slot outside this range until + // it nils the slot, at which point ownership passes to the + // producer. + // + // The head index is stored in the most-significant bits so + // that we can atomically add to it and the overflow is + // harmless. + headTail atomic.Uint64 + + // vals is a ring buffer of interface{} values stored in this + // dequeue. The size of this must be a power of 2. + // + // vals[i].typ is nil if the slot is empty and non-nil + // otherwise. A slot is still in use until *both* the tail + // index has moved beyond it and typ has been set to nil. This + // is set to nil atomically by the consumer and read + // atomically by the producer. + vals []eface +} + +type eface struct { + typ, val unsafe.Pointer +} + +const dequeueBits = 32 + +// dequeueLimit is the maximum size of a poolDequeue. +// +// This must be at most (1<> dequeueBits) & mask) + tail = uint32(ptrs & mask) + return +} + +func (d *poolDequeue) pack(head, tail uint32) uint64 { + const mask = 1<= dequeueLimit { + // Can't make it any bigger. + newSize = dequeueLimit + } + + d2 := &poolChainElt{prev: d} + d2.vals = make([]eface, newSize) + c.head = d2 + storePoolChainElt(&d.next, d2) + d2.pushHead(val) +} + +func (c *poolChain) popHead() (any, bool) { + d := c.head + for d != nil { + if val, ok := d.popHead(); ok { + return val, ok + } + // There may still be unconsumed elements in the + // previous dequeue, so try backing up. + d = loadPoolChainElt(&d.prev) + } + return nil, false +} + +func (c *poolChain) popTail() (any, bool) { + d := loadPoolChainElt(&c.tail) + if d == nil { + return nil, false + } + + for { + // It's important that we load the next pointer + // *before* popping the tail. In general, d may be + // transiently empty, but if next is non-nil before + // the pop and the pop fails, then d is permanently + // empty, which is the only condition under which it's + // safe to drop d from the chain. + d2 := loadPoolChainElt(&d.next) + + if val, ok := d.popTail(); ok { + return val, ok + } + + if d2 == nil { + // This is the only dequeue. It's empty right + // now, but could be pushed to in the future. + return nil, false + } + + // The tail of the chain has been drained, so move on + // to the next dequeue. Try to drop it from the chain + // so the next pop doesn't have to look at the empty + // dequeue again. + if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.tail)), unsafe.Pointer(d), unsafe.Pointer(d2)) { + // We won the race. Clear the prev pointer so + // the garbage collector can collect the empty + // dequeue and so popHead doesn't back up + // further than necessary. + storePoolChainElt(&d2.prev, nil) + } + d = d2 + } +} diff --git a/cannon/testdata/example/mt-pool/runtime.go b/cannon/testdata/example/mt-pool/runtime.go new file mode 100644 index 0000000000000..1b6dbe3e6cd56 --- /dev/null +++ b/cannon/testdata/example/mt-pool/runtime.go @@ -0,0 +1,11 @@ +package main + +import ( + _ "unsafe" // Required for go:linkname +) + +//go:linkname runtime_procPin runtime.procPin +func runtime_procPin() int + +//go:linkname runtime_procUnpin runtime.procUnpin +func runtime_procUnpin() diff --git a/cannon/testdata/example/mt-rwmutex/go.mod b/cannon/testdata/example/mt-rwmutex/go.mod new file mode 100644 index 0000000000000..a0a433e911990 --- /dev/null +++ b/cannon/testdata/example/mt-rwmutex/go.mod @@ -0,0 +1,5 @@ +module rwmutex + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/mt-rwmutex/main.go b/cannon/testdata/example/mt-rwmutex/main.go new file mode 100644 index 0000000000000..8553bba75ef4a --- /dev/null +++ b/cannon/testdata/example/mt-rwmutex/main.go @@ -0,0 +1,226 @@ +// Portions of this code are derived from code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/rwmutex_test.go +// +// --- Original License Notice --- +// +// Copyright 2009 The Go Authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "fmt" + "os" + "runtime" + "sync" + "sync/atomic" +) + +func main() { + TestParallelReaders() + TestRLocker() + TestRWMutex() + + fmt.Println("RWMutex test passed") +} + +func parallelReader(m *sync.RWMutex, clocked, cunlock, cdone chan bool) { + m.RLock() + clocked <- true + <-cunlock + m.RUnlock() + cdone <- true +} + +func doTestParallelReaders(numReaders, gomaxprocs int) { + runtime.GOMAXPROCS(gomaxprocs) + var m sync.RWMutex + clocked := make(chan bool) + cunlock := make(chan bool) + cdone := make(chan bool) + for i := 0; i < numReaders; i++ { + go parallelReader(&m, clocked, cunlock, cdone) + } + // Wait for all parallel RLock()s to succeed. + for i := 0; i < numReaders; i++ { + <-clocked + } + for i := 0; i < numReaders; i++ { + cunlock <- true + } + // Wait for the goroutines to finish. + for i := 0; i < numReaders; i++ { + <-cdone + } +} + +func TestParallelReaders() { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + doTestParallelReaders(1, 4) + doTestParallelReaders(3, 4) + doTestParallelReaders(4, 2) +} + +func reader(rwm *sync.RWMutex, num_iterations int, activity *int32, cdone chan bool) { + for i := 0; i < num_iterations; i++ { + rwm.RLock() + n := atomic.AddInt32(activity, 1) + if n < 1 || n >= 10000 { + rwm.RUnlock() + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -1) + rwm.RUnlock() + } + cdone <- true +} + +func writer(rwm *sync.RWMutex, num_iterations int, activity *int32, cdone chan bool) { + for i := 0; i < num_iterations; i++ { + rwm.Lock() + n := atomic.AddInt32(activity, 10000) + if n != 10000 { + rwm.Unlock() + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -10000) + rwm.Unlock() + } + cdone <- true +} + +func HammerRWMutex(gomaxprocs, numReaders, num_iterations int) { + runtime.GOMAXPROCS(gomaxprocs) + // Number of active readers + 10000 * number of active writers. + var activity int32 + var rwm sync.RWMutex + cdone := make(chan bool) + go writer(&rwm, num_iterations, &activity, cdone) + var i int + for i = 0; i < numReaders/2; i++ { + go reader(&rwm, num_iterations, &activity, cdone) + } + go writer(&rwm, num_iterations, &activity, cdone) + for ; i < numReaders; i++ { + go reader(&rwm, num_iterations, &activity, cdone) + } + // Wait for the 2 writers and all readers to finish. + for i := 0; i < 2+numReaders; i++ { + <-cdone + } +} + +func TestRWMutex() { + var m sync.RWMutex + + m.Lock() + if m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock succeeded with mutex locked") + os.Exit(1) + } + if m.TryRLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryRLock succeeded with mutex locked") + os.Exit(1) + } + m.Unlock() + + if !m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock failed with mutex unlocked") + os.Exit(1) + } + m.Unlock() + + if !m.TryRLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryRLock failed with mutex unlocked") + os.Exit(1) + } + if !m.TryRLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryRLock failed with mutex unlocked") + os.Exit(1) + } + if m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock succeeded with mutex rlocked") + os.Exit(1) + } + m.RUnlock() + m.RUnlock() + + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + n := 5 + + HammerRWMutex(1, 1, n) + HammerRWMutex(1, 3, n) + HammerRWMutex(1, 10, n) + HammerRWMutex(4, 1, n) + HammerRWMutex(4, 3, n) + HammerRWMutex(4, 10, n) + HammerRWMutex(10, 1, n) + HammerRWMutex(10, 3, n) + HammerRWMutex(10, 10, n) + HammerRWMutex(10, 5, n) +} + +func TestRLocker() { + var wl sync.RWMutex + var rl sync.Locker + wlocked := make(chan bool, 1) + rlocked := make(chan bool, 1) + rl = wl.RLocker() + n := 10 + go func() { + for i := 0; i < n; i++ { + rl.Lock() + rl.Lock() + rlocked <- true + wl.Lock() + wlocked <- true + } + }() + for i := 0; i < n; i++ { + <-rlocked + rl.Unlock() + select { + case <-wlocked: + _, _ = fmt.Fprintln(os.Stderr, "RLocker() didn't read-lock it") + os.Exit(1) + default: + } + rl.Unlock() + <-wlocked + select { + case <-rlocked: + _, _ = fmt.Fprintln(os.Stderr, "RLocker() didn't respect the write lock") + os.Exit(1) + default: + } + wl.Unlock() + } +} diff --git a/cannon/testdata/example/mt-value/go.mod b/cannon/testdata/example/mt-value/go.mod new file mode 100644 index 0000000000000..602687cbcca26 --- /dev/null +++ b/cannon/testdata/example/mt-value/go.mod @@ -0,0 +1,8 @@ +module mtvalue + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/mt-value/main.go b/cannon/testdata/example/mt-value/main.go new file mode 100644 index 0000000000000..51fd1b2b73001 --- /dev/null +++ b/cannon/testdata/example/mt-value/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(TestValue, "TestValue") + testutil.RunTest(TestValueLarge, "TestValueLarge") + testutil.RunTest(TestValuePanic, "TestValuePanic") + testutil.RunTest(TestValueConcurrent, "TestValueConcurrent") + testutil.RunTest(TestValue_Swap, "TestValue_Swap") + testutil.RunTest(TestValueSwapConcurrent, "TestValueSwapConcurrent") + testutil.RunTest(TestValue_CompareAndSwap, "TestValue_CompareAndSwap") + testutil.RunTest(TestValueCompareAndSwapConcurrent, "TestValueCompareAndSwapConcurrent") + + fmt.Println("Value tests passed") +} diff --git a/cannon/testdata/example/mt-value/value_test_copy.go b/cannon/testdata/example/mt-value/value_test_copy.go new file mode 100644 index 0000000000000..32ccc00674080 --- /dev/null +++ b/cannon/testdata/example/mt-value/value_test_copy.go @@ -0,0 +1,312 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/atomic/value_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "math/rand" + "runtime" + "strconv" + "sync" + "sync/atomic" + . "sync/atomic" + "testing" + + "utils/testutil" +) + +var short bool = true + +func TestValue(t *testutil.TestRunner) { + var v Value + if v.Load() != nil { + t.Fatal("initial Value is not nil") + } + v.Store(42) + x := v.Load() + if xx, ok := x.(int); !ok || xx != 42 { + t.Fatalf("wrong value: got %+v, want 42", x) + } + v.Store(84) + x = v.Load() + if xx, ok := x.(int); !ok || xx != 84 { + t.Fatalf("wrong value: got %+v, want 84", x) + } +} + +func TestValueLarge(t *testutil.TestRunner) { + var v Value + v.Store("foo") + x := v.Load() + if xx, ok := x.(string); !ok || xx != "foo" { + t.Fatalf("wrong value: got %+v, want foo", x) + } + v.Store("barbaz") + x = v.Load() + if xx, ok := x.(string); !ok || xx != "barbaz" { + t.Fatalf("wrong value: got %+v, want barbaz", x) + } +} + +func TestValuePanic(t *testutil.TestRunner) { + const nilErr = "sync/atomic: store of nil value into Value" + const badErr = "sync/atomic: store of inconsistently typed value into Value" + var v Value + func() { + defer func() { + err := recover() + if err != nilErr { + t.Fatalf("inconsistent store panic: got '%v', want '%v'", err, nilErr) + } + }() + v.Store(nil) + }() + v.Store(42) + func() { + defer func() { + err := recover() + if err != badErr { + t.Fatalf("inconsistent store panic: got '%v', want '%v'", err, badErr) + } + }() + v.Store("foo") + }() + func() { + defer func() { + err := recover() + if err != nilErr { + t.Fatalf("inconsistent store panic: got '%v', want '%v'", err, nilErr) + } + }() + v.Store(nil) + }() +} + +func TestValueConcurrent(t *testutil.TestRunner) { + tests := [][]any{ + {uint16(0), ^uint16(0), uint16(1 + 2<<8), uint16(3 + 4<<8)}, + {uint32(0), ^uint32(0), uint32(1 + 2<<16), uint32(3 + 4<<16)}, + {uint64(0), ^uint64(0), uint64(1 + 2<<32), uint64(3 + 4<<32)}, + {complex(0, 0), complex(1, 2), complex(3, 4), complex(5, 6)}, + } + p := 4 * runtime.GOMAXPROCS(0) + N := int(1e5) + if short { + p /= 2 + //N = 1e3 + N = 1e2 + } + for _, test := range tests { + var v Value + done := make(chan bool, p) + for i := 0; i < p; i++ { + go func() { + r := rand.New(rand.NewSource(rand.Int63())) + expected := true + loop: + for j := 0; j < N; j++ { + x := test[r.Intn(len(test))] + v.Store(x) + x = v.Load() + for _, x1 := range test { + if x == x1 { + continue loop + } + } + t.Logf("loaded unexpected value %+v, want %+v", x, test) + expected = false + break + } + done <- expected + }() + } + for i := 0; i < p; i++ { + if !<-done { + t.FailNow() + } + } + } +} + +func BenchmarkValueRead(b *testing.B) { + var v Value + v.Store(new(int)) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + x := v.Load().(*int) + if *x != 0 { + b.Fatalf("wrong value: got %v, want 0", *x) + } + } + }) +} + +var Value_SwapTests = []struct { + init any + new any + want any + err any +}{ + {init: nil, new: nil, err: "sync/atomic: swap of nil value into Value"}, + {init: nil, new: true, want: nil, err: nil}, + {init: true, new: "", err: "sync/atomic: swap of inconsistently typed value into Value"}, + {init: true, new: false, want: true, err: nil}, +} + +func TestValue_Swap(t *testutil.TestRunner) { + for i, tt := range Value_SwapTests { + t.Run(strconv.Itoa(i), func(t testing.TB) { + var v Value + if tt.init != nil { + v.Store(tt.init) + } + defer func() { + err := recover() + switch { + case tt.err == nil && err != nil: + t.Errorf("should not panic, got %v", err) + case tt.err != nil && err == nil: + t.Errorf("should panic %v, got ", tt.err) + } + }() + if got := v.Swap(tt.new); got != tt.want { + t.Errorf("got %v, want %v", got, tt.want) + } + if got := v.Load(); got != tt.new { + t.Errorf("got %v, want %v", got, tt.new) + } + }) + } +} + +func TestValueSwapConcurrent(t *testutil.TestRunner) { + var v Value + var count uint64 + var g sync.WaitGroup + var m, n uint64 = 10000, 10000 + if short { + //m = 1000 + //n = 1000 + m = 10 + n = 10 + } + for i := uint64(0); i < m*n; i += n { + i := i + g.Add(1) + go func() { + var c uint64 + for new := i; new < i+n; new++ { + if old := v.Swap(new); old != nil { + c += old.(uint64) + } + } + atomic.AddUint64(&count, c) + g.Done() + }() + } + g.Wait() + if want, got := (m*n-1)*(m*n)/2, count+v.Load().(uint64); got != want { + t.Errorf("sum from 0 to %d was %d, want %v", m*n-1, got, want) + } +} + +var heapA, heapB = struct{ uint }{0}, struct{ uint }{0} + +var Value_CompareAndSwapTests = []struct { + init any + new any + old any + want bool + err any +}{ + {init: nil, new: nil, old: nil, err: "sync/atomic: compare and swap of nil value into Value"}, + {init: nil, new: true, old: "", err: "sync/atomic: compare and swap of inconsistently typed values into Value"}, + {init: nil, new: true, old: true, want: false, err: nil}, + {init: nil, new: true, old: nil, want: true, err: nil}, + {init: true, new: "", err: "sync/atomic: compare and swap of inconsistently typed value into Value"}, + {init: true, new: true, old: false, want: false, err: nil}, + {init: true, new: true, old: true, want: true, err: nil}, + {init: heapA, new: struct{ uint }{1}, old: heapB, want: true, err: nil}, +} + +func TestValue_CompareAndSwap(t *testutil.TestRunner) { + for i, tt := range Value_CompareAndSwapTests { + t.Run(strconv.Itoa(i), func(t testing.TB) { + var v Value + if tt.init != nil { + v.Store(tt.init) + } + defer func() { + err := recover() + switch { + case tt.err == nil && err != nil: + t.Errorf("got %v, wanted no panic", err) + case tt.err != nil && err == nil: + t.Errorf("did not panic, want %v", tt.err) + } + }() + if got := v.CompareAndSwap(tt.old, tt.new); got != tt.want { + t.Errorf("got %v, want %v", got, tt.want) + } + }) + } +} + +func TestValueCompareAndSwapConcurrent(t *testutil.TestRunner) { + var v Value + var w sync.WaitGroup + v.Store(0) + m, n := 1000, 100 + if short { + //m = 100 + //n = 100 + m = 10 + n = 10 + } + for i := 0; i < m; i++ { + i := i + w.Add(1) + go func() { + for j := i; j < m*n; runtime.Gosched() { + if v.CompareAndSwap(j, j+1) { + j += m + } + } + w.Done() + }() + } + w.Wait() + if stop := v.Load().(int); stop != m*n { + t.Errorf("did not get to %v, stopped at %v", m*n, stop) + } +} diff --git a/cannon/testdata/example/mt-wg/go.mod b/cannon/testdata/example/mt-wg/go.mod new file mode 100644 index 0000000000000..0c10638b3d1d6 --- /dev/null +++ b/cannon/testdata/example/mt-wg/go.mod @@ -0,0 +1,8 @@ +module wg + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/mt-wg/main.go b/cannon/testdata/example/mt-wg/main.go new file mode 100644 index 0000000000000..cfdb3e56b8ed4 --- /dev/null +++ b/cannon/testdata/example/mt-wg/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(TestWaitGroup, "TestWaitGroup") + testutil.RunTest(TestWaitGroupMisuse, "TestWaitGroupMisuse") + testutil.RunTest(TestWaitGroupRace, "TestWaitGroupRace") + testutil.RunTest(TestWaitGroupAlign, "TestWaitGroupAlign") + + fmt.Println("WaitGroup tests passed") +} diff --git a/cannon/testdata/example/mt-wg/waitgroup_test_copy.go b/cannon/testdata/example/mt-wg/waitgroup_test_copy.go new file mode 100644 index 0000000000000..81f0cd0ce4cd5 --- /dev/null +++ b/cannon/testdata/example/mt-wg/waitgroup_test_copy.go @@ -0,0 +1,130 @@ +// This file is based on code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/waitgroup_test.go +// +// --- Original License Notice --- +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + . "sync" + "sync/atomic" + "testing" + + "utils/testutil" +) + +func testWaitGroup(t testing.TB, wg1 *WaitGroup, wg2 *WaitGroup) { + n := 16 + wg1.Add(n) + wg2.Add(n) + exited := make(chan bool, n) + for i := 0; i != n; i++ { + go func() { + wg1.Done() + wg2.Wait() + exited <- true + }() + } + wg1.Wait() + for i := 0; i != n; i++ { + select { + case <-exited: + t.Fatal("WaitGroup released group too soon") + default: + } + wg2.Done() + } + for i := 0; i != n; i++ { + <-exited // Will block if barrier fails to unlock someone. + } +} + +func TestWaitGroup(t *testutil.TestRunner) { + wg1 := &WaitGroup{} + wg2 := &WaitGroup{} + + // Run the same test a few times to ensure barrier is in a proper state. + for i := 0; i != 8; i++ { + testWaitGroup(t, wg1, wg2) + } +} + +func TestWaitGroupMisuse(t *testutil.TestRunner) { + defer func() { + err := recover() + if err != "sync: negative WaitGroup counter" { + t.Fatalf("Unexpected panic: %#v", err) + } + }() + wg := &WaitGroup{} + wg.Add(1) + wg.Done() + wg.Done() + t.Fatal("Should panic") +} + +func TestWaitGroupRace(t *testutil.TestRunner) { + // Run this test for about 1ms. + for i := 0; i < 1000; i++ { + wg := &WaitGroup{} + n := new(int32) + // spawn goroutine 1 + wg.Add(1) + go func() { + atomic.AddInt32(n, 1) + wg.Done() + }() + // spawn goroutine 2 + wg.Add(1) + go func() { + atomic.AddInt32(n, 1) + wg.Done() + }() + // Wait for goroutine 1 and 2 + wg.Wait() + if atomic.LoadInt32(n) != 2 { + t.Fatal("Spurious wakeup from Wait") + } + } +} + +func TestWaitGroupAlign(t *testutil.TestRunner) { + type X struct { + x byte + wg WaitGroup + } + var x X + x.wg.Add(1) + go func(x *X) { + x.wg.Done() + }(&x) + x.wg.Wait() +} diff --git a/cannon/testdata/example/utilscheck/go.mod b/cannon/testdata/example/utilscheck/go.mod new file mode 100644 index 0000000000000..5a109a7ca8cd1 --- /dev/null +++ b/cannon/testdata/example/utilscheck/go.mod @@ -0,0 +1,8 @@ +module utilscheck + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck/main.go b/cannon/testdata/example/utilscheck/main.go new file mode 100644 index 0000000000000..ad66a3211ad87 --- /dev/null +++ b/cannon/testdata/example/utilscheck/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(ShouldFail, "ShouldFail") + + fmt.Println("Passed test that should have failed") +} + +func ShouldFail(t *testutil.TestRunner) { + t.Fail() +} diff --git a/cannon/testdata/example/utilscheck2/go.mod b/cannon/testdata/example/utilscheck2/go.mod new file mode 100644 index 0000000000000..ee0430168ad14 --- /dev/null +++ b/cannon/testdata/example/utilscheck2/go.mod @@ -0,0 +1,8 @@ +module utilscheck2 + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck2/main.go b/cannon/testdata/example/utilscheck2/main.go new file mode 100644 index 0000000000000..6fc619f18fe2f --- /dev/null +++ b/cannon/testdata/example/utilscheck2/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "testing" + + "utils/testutil" +) + +func main() { + testutil.RunTest(ShouldFail, "ShouldFail") + + fmt.Println("Passed test that should have failed") +} + +func ShouldFail(t *testutil.TestRunner) { + t.Run("subtest 1", func(t testing.TB) { + // Do something + }) + + t.Run("subtest 2", func(t testing.TB) { + t.Fail() + }) +} diff --git a/cannon/testdata/example/utilscheck3/go.mod b/cannon/testdata/example/utilscheck3/go.mod new file mode 100644 index 0000000000000..3bc116499beb5 --- /dev/null +++ b/cannon/testdata/example/utilscheck3/go.mod @@ -0,0 +1,8 @@ +module utilscheck3 + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck3/main.go b/cannon/testdata/example/utilscheck3/main.go new file mode 100644 index 0000000000000..248c891808b07 --- /dev/null +++ b/cannon/testdata/example/utilscheck3/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "testing" + + "utils/testutil" +) + +func main() { + testutil.RunTest(ShouldFail, "ShouldFail") + + fmt.Println("Passed test that should have failed") +} + +func ShouldFail(t *testutil.TestRunner) { + t.Run("panic test", func(t testing.TB) { + panic("oops") + }) +} diff --git a/cannon/testdata/example/utilscheck4/go.mod b/cannon/testdata/example/utilscheck4/go.mod new file mode 100644 index 0000000000000..7f80460beb9e6 --- /dev/null +++ b/cannon/testdata/example/utilscheck4/go.mod @@ -0,0 +1,8 @@ +module utilscheck4 + +go 1.22 + +toolchain go1.22.0 + +require utils v0.0.0 +replace utils => ../../utils diff --git a/cannon/testdata/example/utilscheck4/main.go b/cannon/testdata/example/utilscheck4/main.go new file mode 100644 index 0000000000000..deb78e2cb4d38 --- /dev/null +++ b/cannon/testdata/example/utilscheck4/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + + "utils/testutil" +) + +func main() { + testutil.RunTest(ShouldFail, "ShouldFail") + + fmt.Println("Passed test that should have failed") +} + +func ShouldFail(t *testutil.TestRunner) { + panic("oops") +} diff --git a/cannon/testdata/utils/go.mod b/cannon/testdata/utils/go.mod new file mode 100644 index 0000000000000..45f262e0b16d0 --- /dev/null +++ b/cannon/testdata/utils/go.mod @@ -0,0 +1,5 @@ +module utils + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/utils/testutil/testing.go b/cannon/testdata/utils/testutil/testing.go new file mode 100644 index 0000000000000..f6c79c63655eb --- /dev/null +++ b/cannon/testdata/utils/testutil/testing.go @@ -0,0 +1,170 @@ +package testutil + +import ( + "fmt" + "os" + "runtime" + "sync" + "testing" +) + +func RunTest(testFunc func(*TestRunner), name string) { + goRunTest(name, testFunc, newTestRunner(name)) +} + +type TestRunner struct { + *mockT + baseName string +} + +func newTestRunner(baseName string) *TestRunner { + return &TestRunner{mockT: newMockT(), baseName: baseName} +} + +func (r *TestRunner) Run(name string, testFunc func(t testing.TB)) bool { + testName := r.baseName + if name != "" { + testName = fmt.Sprintf("%v (%v)", r.baseName, name) + } + + var tester testing.TB = r + goRunTest(testName, testFunc, tester) + return !r.Failed() +} + +func goRunTest[T testing.TB](testName string, testFunc func(t T), t T) { + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer func() { + if err := recover(); err != nil { + fmt.Printf("Test panicked: %v\n\t%v", testName, err) + os.Exit(1) + } + + if t.Failed() { + fmt.Printf("Test failed: %v\n", testName) + os.Exit(1) + } else if t.Skipped() { + fmt.Printf("Test skipped: %v\n", testName) + } else { + fmt.Printf("Test passed: %v\n", testName) + } + + wg.Done() + }() + + testFunc(t) + }() + + wg.Wait() +} + +type mockT struct { + *testing.T + mu sync.Mutex + failed bool + skipped bool +} + +var _ testing.TB = (*mockT)(nil) + +func newMockT() *mockT { + return &mockT{} +} + +func (t *mockT) Cleanup(func()) { + t.Fatalf("Cleanup not supported") +} + +func (t *mockT) Error(args ...any) { + fmt.Print(args...) + t.fail() +} + +func (t *mockT) Errorf(format string, args ...any) { + fmt.Printf(format, args...) + t.fail() +} + +func (t *mockT) Fail() { + t.fail() +} + +func (t *mockT) FailNow() { + fmt.Println("Fatal") + t.fail() +} + +func (t *mockT) Failed() bool { + t.mu.Lock() + defer t.mu.Unlock() + return t.failed +} + +func (t *mockT) Fatal(args ...any) { + fmt.Print(args...) + t.fail() +} + +func (t *mockT) Fatalf(format string, args ...any) { + fmt.Printf(format, args...) + t.fail() +} + +func (t *mockT) Helper() {} + +func (t *mockT) Log(args ...any) { + fmt.Print(args...) +} + +func (t *mockT) Logf(format string, args ...any) { + fmt.Printf(format, args...) +} + +func (t *mockT) Name() string { + return "" +} + +func (t *mockT) Setenv(key, value string) { + t.Fatalf("Setenv not supported") +} + +func (t *mockT) Skip(args ...any) { + fmt.Println(args...) + t.skip() +} + +func (t *mockT) SkipNow() { + t.skip() +} + +func (t *mockT) Skipf(format string, args ...any) { + fmt.Printf(format, args...) + t.skip() +} +func (t *mockT) Skipped() bool { + t.mu.Lock() + defer t.mu.Unlock() + return t.skipped +} + +func (t *mockT) skip() { + t.mu.Lock() + defer t.mu.Unlock() + t.skipped = true + runtime.Goexit() +} + +func (t *mockT) fail() { + t.mu.Lock() + defer t.mu.Unlock() + t.failed = true + runtime.Goexit() +} + +func (t *mockT) TempDir() string { + t.Fatalf("TempDir not supported") + return "" +} diff --git a/cloudbuild.yaml b/cloudbuild.yaml deleted file mode 100644 index 2cf6dfb333bb8..0000000000000 --- a/cloudbuild.yaml +++ /dev/null @@ -1,12 +0,0 @@ -steps: - - name: 'gcr.io/kaniko-project/executor:latest' - args: - - --destination=us-central1-docker.pkg.dev/$PROJECT_ID/images/deployer-bedrock:$_TAG - - --destination=us-central1-docker.pkg.dev/$PROJECT_ID/images/deployer-bedrock:$COMMIT_SHA - - --dockerfile=./ops/docker/Dockerfile.packages - - --target=deployer-bedrock - - --cache=true - - --cache-ttl=48h - waitFor: ['-'] -options: - machineType: N1_HIGHCPU_32 diff --git a/codecov.yml b/codecov.yml index 96b195edabf90..8fd1bc517731a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,5 +1,6 @@ codecov: require_ci_to_pass: false + comment: layout: "diff, flags, files" behavior: default @@ -12,8 +13,14 @@ ignore: - "**/*.t.sol" - "packages/contracts-bedrock/test/**/*.sol" - "packages/contracts-bedrock/scripts/**/*.sol" - - "packages/contracts-bedrock/contracts/vendor/WETH9.sol" - - 'packages/contracts-bedrock/contracts/EAS/**/*.sol' + - "packages/contracts-bedrock/src/vendor/**/*.sol" + - "packages/contracts-bedrock/src/interfaces/**/*.sol" + # TODO: add coverage for MIPS64 back once tests are merged in + - "packages/contracts-bedrock/src/cannon/MIPS64.sol" + - "packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol" + - "packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol" + - "packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol" + coverage: status: patch: @@ -26,16 +33,14 @@ coverage: project: default: informational: true + flag_management: # Note: flags should have the same name as the circleci job in which they # are uploaded. individual_flags: - - name: contracts-bedrock-tests + - name: contracts-bedrock-coverage paths: - packages/contracts-bedrock/src statuses: - type: patch target: 100% - - name: bedrock-go-tests - - name: contracts-tests - - name: sdk-tests diff --git a/da-server b/da-server new file mode 160000 index 0000000000000..a8fffd8bffd96 --- /dev/null +++ b/da-server @@ -0,0 +1 @@ +Subproject commit a8fffd8bffd96552cec1880090575adadd0ce43a diff --git a/docker-bake.hcl b/docker-bake.hcl index 5740590a95f21..64e22327629ce 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -6,6 +6,10 @@ variable "REPOSITORY" { default = "oplabs-tools-artifacts/images" } +variable "KONA_VERSION" { + default = "kona-client-v0.1.0-beta.5" +} + variable "GIT_COMMIT" { default = "dev" } @@ -119,6 +123,7 @@ target "op-challenger" { GIT_COMMIT = "${GIT_COMMIT}" GIT_DATE = "${GIT_DATE}" OP_CHALLENGER_VERSION = "${OP_CHALLENGER_VERSION}" + KONA_VERSION="${KONA_VERSION}" } target = "op-challenger-target" platforms = split(",", PLATFORMS) @@ -206,15 +211,25 @@ target "proofs-tools" { dockerfile = "./ops/docker/proofs-tools/Dockerfile" context = "." args = { - CHALLENGER_VERSION="v1.1.2-rc.1" - KONA_VERSION="kona-client-v0.1.0-alpha.3" - ASTERISC_VERSION="v1.0.2" + CHALLENGER_VERSION="b46bffed42db3442d7484f089278d59f51503049" + KONA_VERSION="${KONA_VERSION}" } target="proofs-tools" platforms = split(",", PLATFORMS) tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/proofs-tools:${tag}"] } +target "holocene-deployer" { + dockerfile = "./packages/contracts-bedrock/scripts/upgrades/holocene/upgrade.dockerfile" + context = "./packages/contracts-bedrock/scripts/upgrades/holocene" + args = { + REV = "op-contracts/v1.8.0-rc.1" + } + target="holocene-deployer" + platforms = split(",", PLATFORMS) + tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/holocene-deployer:${tag}"] +} + target "ci-builder" { dockerfile = "./ops/docker/ci-builder/Dockerfile" context = "." @@ -231,15 +246,6 @@ target "ci-builder-rust" { tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/ci-builder-rust:${tag}"] } -target "contracts-bedrock" { - dockerfile = "./ops/docker/Dockerfile.packages" - context = "." - target = "contracts-bedrock" - # See comment in Dockerfile.packages for why we only build for linux/amd64. - platforms = ["linux/amd64"] - tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/contracts-bedrock:${tag}"] -} - target "op-deployer" { dockerfile = "ops/docker/op-stack-go/Dockerfile" context = "." diff --git a/docs/handbook/pr-guidelines.md b/docs/handbook/pr-guidelines.md index cf133ada6c57e..57913ebeaa517 100644 --- a/docs/handbook/pr-guidelines.md +++ b/docs/handbook/pr-guidelines.md @@ -46,4 +46,4 @@ This is organized by current state of PR, so it can be easily referenced frequen ### Merging PRs - **Resolve all Comments**: Comments can be resolved by (1) the PR author for nits/optionals, (2) the author or reviewer after discussions, or (3) extracting the comment into an issue to address in a future PR. For (3), ensure the new issue links to the specific comment thread. This is currently enforced by GitHub's merge requirements. -- **Other Standard Merge Requirements**: The PR must be approved by the appropriate reviewers, CI must passing, and other standard merge requirements apply. +- **Other Standard Merge Requirements**: The PR must be approved by the appropriate reviewers, CI must pass, and other standard merge requirements apply. diff --git a/docs/postmortems/2022-02-02-inflation-vuln.md b/docs/postmortems/2022-02-02-inflation-vuln.md index a755b0fdfe38f..a2a23e382303b 100644 --- a/docs/postmortems/2022-02-02-inflation-vuln.md +++ b/docs/postmortems/2022-02-02-inflation-vuln.md @@ -58,7 +58,7 @@ timeline and activities were as follows: (Using github handles as identifiers) - 2022-02-02 1625: smartcontracts receives an e-mail from saurik claiming to have found a critical - issue in L2Geth. E-mail was sent to securityoptimism.io. + issue in L2Geth. E-mail was sent to security@optimism.io. - 2022-02-02 X: saurik messaged smartcontracts on Discord to make sure we checked the e-mail since he knew we had a prior problem where security advisories went to spam. - 2022-02-02 1650: Huddle begins in #security on Slack. diff --git a/docs/security-reviews/2024_08_report-cantinacode-coinbase-fault-proofs-mips.pdf b/docs/security-reviews/2024_08_Fault-Proofs-MIPS_Cantina.pdf similarity index 100% rename from docs/security-reviews/2024_08_report-cantinacode-coinbase-fault-proofs-mips.pdf rename to docs/security-reviews/2024_08_Fault-Proofs-MIPS_Cantina.pdf diff --git a/docs/security-reviews/2024_08_report-cb-fault-proofs-non-mips.pdf b/docs/security-reviews/2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf similarity index 100% rename from docs/security-reviews/2024_08_report-cb-fault-proofs-non-mips.pdf rename to docs/security-reviews/2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf diff --git a/docs/security-reviews/2024_10-Cannon-FGETFD-3DocSecurity.md b/docs/security-reviews/2024_10-Cannon-FGETFD-3DocSecurity.md new file mode 100644 index 0000000000000..f157356e4905f --- /dev/null +++ b/docs/security-reviews/2024_10-Cannon-FGETFD-3DocSecurity.md @@ -0,0 +1,114 @@ +# Audit Report - OP Cannon + +| | | +| -------------- | ------------------------------------------------------------------------- | +| **Audit Date** | Oct 2nd 2024 - Oct 3rd 2024 | +| **Auditor** | 3DOC Security ([@3docSec](https://x.com/3docSec)) | +| **Version 1** | Oct 3rd 2024. | + +
+ +# Contents +- [Audit Report - OP cannon](#audit-report---op-cannon) +- [Contents](#contents) +- [Disclaimer](#disclaimer) +- [About 3DOC](#about-3doc) +- [Scope](#scope) +- [Severity Classification](#severity-classification) +- [Summary](#summary) +- [Findings](#findings) + - [Low Risk Findings (1)](#low-risk-findings-1) + - [1. Op-challenger Docker image does not include Cannon embeds](#-op-challenger-docker-image-does-not-include-cannon-embeds) + +# Disclaimer +_The following audit report is based on the information and code provided by the client, and any findings or recommendations are made solely on the basis of this information. While the Auditor has exercised due care and skill in conducting the audit, it cannot be guaranteed that all issues have been identified and that there are no undiscovered errors or vulnerabilities in the code._ + +_Furthermore, this report is not an endorsement or certification of the protocol, and the Auditor does not assume any responsibility for any losses or damages that may result from the use of the smart contracts, either in their current form or in any modified version thereof._ + +# About 3DOC +3DOC is a top ranked Smart Contract Auditor doing audits on Code4rena (www.code4rena.com), having ranked 1st in multiple contests in [solo](https://code4rena.com/@3docSec) and [team](https://code4rena.com/@RadiantLabs) audits, including the [Optimism superchain contest](https://code4rena.com/audits/2024-07-optimism-superchain) in July 2024.
+He can also be booked for conducting Private Audits. + +Contact:
+ +X: [@3DocSec](https://x.com/3DocSec) + +e-mail: [hello@3doc.fr](mailto:hello@3doc.fr) + +# Scope +The scope of the audit is the following Pull Request in the client's GitHub repository: + +https://github.com/ethereum-optimism/optimism/pull/12050 + +The change consists of a core update for supporting the `F_GETFD` syscall in the MIPS VM, [provided with this commit](https://github.com/ethereum-optimism/optimism/pull/12050/commits/7c8257d3574a2a76ab90f8129c7b532d68049944), and several additional updates accommodating the VM version bump that came with the core change. + +# Severity Classification +| Severity | Impact: High | Impact: Medium | Impact: Low | +| ---------------------- | ------------ | -------------- | ----------- | +| **Likelihood: High** | ![high] | ![high] | ![medium] | +| **Likelihood: Medium** | ![high] | ![medium] | ![low] | +| **Likelihood: Low** | ![medium] | ![low] | ![low] | + +**Impact** - the technical, economic and reputation damage of a successful attack + +**Likelihood** - the chance that a particular vulnerability is discovered and exploited + +# Summary + +| Severity | Total | +| -------------- | ----- | +| ![high] | 0 | +| ![medium] | 0 | +| ![low] | 0 | +| ![information] | 0 | + + +# Findings +## Low Risk findings (0) + +### [False positive] Op-challenger Docker image does not include Cannon embeds +#### Description +The change in scope added a new implementation of the Cannon VM, which was called `VersionSingleThreaded2`. Cannon has now three versions (`VersionSingleThreaded`, `VersionSingleThreaded2`, and `VersionMultiThreaded`). + +The op-challenger program makes use of the Cannon VM in several places via the configured `VmBin` path, which point to the `multicannon` command line. This one reads the State version from the input state and selects the right Cannon VM accordingly (`cannon/multicannon/exec.go:L81`). + +If we look at the Docker challenger image generated by the `make golang-docker` command, however, we can see it doesn't contain an `embeds` folder: + +``` +docker run -t us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger find / -name embeds +``` + +But it however has the `cannon` command pointing to the `multicannon` multiplexer: + +``` +➜ optimism git:(52d0e60c1) ✗ docker run -t us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger cannon | head -2 +NAME: + multicannon - MIPS Fault Proof tool +➜ optimism git:(52d0e60c1) ✗ +``` + +This issue appears to be pre-existing to the changes in scope; using Docker images to run the challenger is [mentioned as option](https://docs.optimism.io/builders/chain-operators/tools/op-challenger), but only as alternative option, hence the Low risk assessed for this finding. + +#### Impact +Because of this issue, challenger instances operated in a Docker container won't be able to function properly. + +#### Recommendation +Consider modifying the Docker build chain to include the `embeds` folder. +Consider extending the current e2e test suite to cover execution from Docker images. + +#### Discussion + +> @inphi The cannon-2 implementation that supports go1.22 is now embedded into the cannon cli binary. Note that these embeds are not actual files that you can find in the docker container filesystem. But rather an embedded filesystem inside the Go binary - https://pkg.go.dev/embed. + +> @3DOC Oh yes I see that. So those are included in an embedded filesystem, I missed that + + +[high]: https://img.shields.io/badge/-HIGH-b02319 "HIGH" +[medium]: https://img.shields.io/badge/-MEDIUM-orange "MEDIUM" +[low]: https://img.shields.io/badge/-LOW-FFD700 "LOW" +[information]: https://img.shields.io/badge/-INFORMATION-darkgreen "INFORMATION" +[fixed]: https://img.shields.io/badge/-FIXED-brightgreen "FIXED" +[acknowledged]: https://img.shields.io/badge/-ACKNOWLEDGED-blue "ACKNOWLEDGED" +[disputed]: https://img.shields.io/badge/-DISPUTED-lightgrey "DISPUTED" +[reported]: https://img.shields.io/badge/-REPORTED-lightblue "REPORTED" +[partiallyfixed]: https://img.shields.io/badge/-PARTIALLY_FIXED-lightgreen "PARTIALLTY FIXED" diff --git a/docs/security-reviews/README.md b/docs/security-reviews/README.md index fd41977b9f109..265d0a65f9038 100644 --- a/docs/security-reviews/README.md +++ b/docs/security-reviews/README.md @@ -5,27 +5,28 @@ The following is a list of past security reviews. Each review is focused on a different part of the codebase, and at a different point in time. Please see the report for the specific details. -| Date | Reviewer | Focus | Report Link | -| ------- | -------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 2020-10 | Trail of Bits | Rollup | [2020_10-TrailOfBits.pdf](./2020_10-Rollup-TrailOfBits.pdf) | -| 2020-11 | Dapphub | ECDSA Wallet | [2020_11-Dapphub-ECDSA_Wallet.pdf](./2020_11-Dapphub-ECDSA_Wallet.pdf) | -| 2021-03 | OpenZeppelin | OVM and Rollup | [2021_03-OVM_and_Rollup-OpenZeppelin.pdf](./2021_03-OVM_and_Rollup-OpenZeppelin.pdf) | -| 2021-03 | ConsenSys Diligence | Safety Checker | [2021_03-SafetyChecker-ConsenSysDiligence.pdf](./2021_03-SafetyChecker-ConsenSysDiligence.pdf) | -| 2022-05 | Zeppelin | Bedrock Contracts | [2022_05-Bedrock_Contracts-Zeppelin.pdf](./2022_05-Bedrock_Contracts-Zeppelin.pdf) | -| 2022-05 | Trail of Bits | OpNode | [2022_05-OpNode-TrailOfBits.pdf](./2022_05-OpNode-TrailOfBits.pdf) | -| 2022-08 | Sigma Prime | Bedrock GoLang | [2022_08-Bedrock_GoLang-SigmaPrime.pdf](./2022_08-Bedrock_GoLang-SigmaPrime.pdf) | -| 2022-09 | Zeppelin | Bedrock and Periphery | [2022_09-Bedrock_and_Periphery-Zeppelin.pdf](./2022_09-Bedrock_and_Periphery-Zeppelin.pdf) | -| 2022-10 | Spearbit | Drippie | [2022_10-Drippie-Spearbit.pdf](./2022_10-Drippie-Spearbit.pdf) | -| 2022-11 | Trail of Bits | Invariant Testing | [2022_11-Invariant_Testing-TrailOfBits.pdf](./2022_11-Invariant_Testing-TrailOfBits.pdf) | -| 2022-12 | Runtime Verification | Deposit Transaction | [2022_12-DepositTransaction-RuntimeVerification.pdf](./2022_12-DepositTransaction-RuntimeVerification.pdf) | -| 2023-01 | Trail of Bits | Bedrock Updates | [2023_01-Bedrock_Updates-TrailOfBits.pdf](./2023_01-Bedrock_Updates-TrailOfBits.pdf) | -| 2023-01 | Sherlock | Bedrock | [Sherlock Bedrock Contest](https://audits.sherlock.xyz/contests/38) | -| 2023-03 | Sherlock | Bedrock Fixes | [Sherlock Bedrock Contest - Fix Review](https://audits.sherlock.xyz/contests/63) | -| 2023-12 | Trust | Superchain Config Upgrade | [2023_12_SuperchainConfigUpgrade_Trust.pdf](./2023_12_SuperchainConfigUpgrade_Trust.pdf) | -| 2024-02 | Runtime Verification | Pausability | [Kontrol Verification][kontrol] | -| 2024-02 | Cantina | MCP L1 | [2024_02-MCP_L1-Cantina.pdf](./2024_02-MCP_L1-Cantina.pdf) | -| 2024-03 | Sherlock | MCP L1 | [Sherlock Optimism Fault Proofs Contest](https://audits.sherlock.xyz/contests/205) | -| 2024-08 | Cantina | Fault proof MIPS | [Base Fault Proof MIPS](./2024_08_report-cantinacode-coinbase-fault-proofs-mips.pdf) -| 2024-08 | Spearbit | Fault proof no-MIPS | [Base Fault Proof No MIPS](./2024_08_report-cb-fault-proofs-non-mips.pdf) +| Date | Reviewer | Focus and Scope | Report Link | Commit | Subsequent Release | +|---------|----------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------| -------------------------------------------- |---------------------| +| 2020-10 | Trail of Bits | Rollup | [2020_10-TrailOfBits.pdf](./2020_10-Rollup-TrailOfBits.pdf) | | | +| 2020-11 | Dapphub | ECDSA Wallet | [2020_11-Dapphub-ECDSA_Wallet.pdf](./2020_11-Dapphub-ECDSA_Wallet.pdf) | | | +| 2021-03 | OpenZeppelin | OVM and Rollup | [2021_03-OVM_and_Rollup-OpenZeppelin.pdf](./2021_03-OVM_and_Rollup-OpenZeppelin.pdf) | | | +| 2021-03 | ConsenSys Diligence | Safety Checker | [2021_03-SafetyChecker-ConsenSysDiligence.pdf](./2021_03-SafetyChecker-ConsenSysDiligence.pdf) | | | +| 2022-05 | Zeppelin | Bedrock Contracts | [2022_05-Bedrock_Contracts-Zeppelin.pdf](./2022_05-Bedrock_Contracts-Zeppelin.pdf) | | | +| 2022-05 | Trail of Bits | OpNode | [2022_05-OpNode-TrailOfBits.pdf](./2022_05-OpNode-TrailOfBits.pdf) | | | +| 2022-08 | Sigma Prime | Bedrock GoLang | [2022_08-Bedrock_GoLang-SigmaPrime.pdf](./2022_08-Bedrock_GoLang-SigmaPrime.pdf) | | | +| 2022-09 | Zeppelin | Bedrock and Periphery: All contracts in `packages/contracts-bedrock/contracts` | [2022_09-Bedrock_and_Periphery-Zeppelin.pdf](./2022_09-Bedrock_and_Periphery-Zeppelin.pdf) | 93d3bd411a8ae75702539ac9c5fe00bad21d4104 | op-contracts/v1.0.0 | +| 2022-10 | Spearbit | Drippie: `Drippie.sol` | [2022_10-Drippie-Spearbit.pdf](./2022_10-Drippie-Spearbit.pdf) | 2a7be367634f147736f960eb2f38a77291cdfcad | op-contracts/v1.0.0 | +| 2022-11 | Trail of Bits | Invariant Testing: `OptimismPortal.sol` | [2022_11-Invariant_Testing-TrailOfBits.pdf](./2022_11-Invariant_Testing-TrailOfBits.pdf) | b31d35b67755479645dd150e7cc8c6710f0b4a56 | op-contracts/v1.0.0 | +| 2022-12 | Runtime Verification | Deposit Transaction: `OptimismPortal.sol` | [2022_12-DepositTransaction-RuntimeVerification.pdf](./2022_12-DepositTransaction-RuntimeVerification.pdf) | | op-contracts/v1.0.0 | +| 2023-01 | Trail of Bits | Bedrock Updates: `SystemConfig.sol` | [2023_01-Bedrock_Updates-TrailOfBits.pdf](./2023_01-Bedrock_Updates-TrailOfBits.pdf) | ee96ff8585699b054c95c6ff4a2411ee9fedcc87 | op-contracts/v1.0.0 | +| 2023-01 | Sherlock | Bedrock: All contracts in `packages/contracts-bedrock/src` | Sherlock Bedrock Contest ([site](https://audits.sherlock.xyz/contests/38), [repo](https://github.com/sherlock-audit/2023-01-optimism)) | 3f4b3c328153a8aa03611158b6984d624b17c1d9 | op-contracts/v1.0.0 | +| 2023-03 | Sherlock | Bedrock Fixes: All contracts in `packages/contracts-bedrock/src` | Sherlock Bedrock Contest: Fix Review ([site](https://audits.sherlock.xyz/contests/63), [repo](https://github.com/sherlock-audit/2023-03-optimism)) | 20229b9f78c6613c6ee53b93ca43c71bb74479f4b975 | op-contracts/v1.0.0 | +| 2023-12 | Trust | Superchain Config Upgrade: `SuperchainConfig.sol`, `L1CrossDomainMessenger.sol`, `L1ERC721Bridge.sol`, `L1StandardBridge.sol`, `OptimismPortal.sol`, `CrossDomainMessenger.sol`, `ERC721Bridge.sol`, `StandardBridge.sol` | [2023_12_SuperchainConfigUpgrade_Trust.pdf](./2023_12_SuperchainConfigUpgrade_Trust.pdf) | d1651bb22645ebd41ac4bb2ab4786f9a56fc1003 | op-contracts/v1.2.0 | +| 2024-02 | Runtime Verification | Pausability | [Kontrol Verification][kontrol] | | | +| 2024-02 | Cantina | MCP L1: `OptimismPortal.sol`, `L1CrossDomainMessenger.sol`, `L1StandardBridge.sol`, `L1ERC721Bridge.sol`, `OptimismMintableERC20Factory.sol`, `L2OutputOracle.sol`, `SystemConfig.sol` | [2024_02-MCP_L1-Cantina.pdf](./2024_02-MCP_L1-Cantina.pdf) | e6ef3a900c42c8722e72c2e2314027f85d12ced5 | op-contracts/v1.3.0 | +| 2024-03 | Sherlock | Fault Proofs | Sherlock Optimism Fault Proofs Contest ([site](https://audits.sherlock.xyz/contests/205), [repo](https://github.com/sherlock-audit/2024-02-optimism-2024)) | | | +| 2024-08 | Cantina | Fault proof MIPS: `MIPS.sol` | [./2024_08_Fault-Proofs-MIPS_Cantina.pdf](./2024_08_Fault-Proofs-MIPS_Cantina.pdf) | 71b93116738ee98c9f8713b1a5dfe626ce06c1b2 | op-contracts/v1.4.0 | +| 2024-08 | Spearbit | Fault proof no-MIPS: All contracts in the `packages/contracts-bedrock/src/dispute` directory | [./2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf](./2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf) | 1f7081798ce2d49b8643514663d10681cb853a3d | op-contracts/v1.6.0 | +| 2024-10 | 3Doc Security | Fault proof MIPS: `MIPS.sol` | [./2024_10-Cannon-FGETFD-3DocSecurity.md](./2024_10-Cannon-FGETFD-3DocSecurity.md) | 52d0e60c16498ad4efec8798e3fc1b36b13f46a2 | op-contracts/v1.8.0 | [kontrol]: https://github.com/ethereum-optimism/optimism/blob/876e16ad04968f0bb641eb76f98eb77e7e1a3e16/packages/contracts-bedrock/test/kontrol/README.md diff --git a/go.mod b/go.mod index 66079a8d5daaa..d93d278d5fe73 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,13 @@ module github.com/ethereum-optimism/optimism -go 1.22 +go 1.22.0 toolchain go1.22.7 require ( github.com/BurntSushi/toml v1.4.0 github.com/andybalholm/brotli v1.1.0 + github.com/bmatcuk/doublestar/v4 v4.7.1 github.com/btcsuite/btcd v0.24.2 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/cockroachdb/pebble v1.1.2 @@ -14,9 +15,10 @@ require ( github.com/crate-crypto/go-kzg-4844 v1.0.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 - github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240910145426-b3905c89e8ac + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048 github.com/ethereum/go-ethereum v1.14.11 - github.com/fsnotify/fsnotify v1.7.0 + github.com/ethstorage/da-server v0.0.0-20240925084712-169d238000e5 + github.com/fsnotify/fsnotify v1.8.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 @@ -27,31 +29,30 @@ require ( github.com/holiman/uint256 v1.3.1 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.0 - github.com/klauspost/compress v1.17.9 - github.com/kurtosis-tech/kurtosis/api/golang v1.3.0 + github.com/klauspost/compress v1.17.11 + github.com/kurtosis-tech/kurtosis/api/golang v1.4.3 github.com/libp2p/go-libp2p v0.36.2 github.com/libp2p/go-libp2p-mplex v0.9.0 github.com/libp2p/go-libp2p-pubsub v0.12.0 github.com/libp2p/go-libp2p-testing v0.12.0 github.com/mattn/go-isatty v0.0.20 - github.com/minio/minio-go/v7 v7.0.77 + github.com/minio/minio-go/v7 v7.0.81 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.13.0 - github.com/multiformats/go-multiaddr-dns v0.4.0 + github.com/multiformats/go-multiaddr v0.14.0 + github.com/multiformats/go-multiaddr-dns v0.4.1 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 github.com/pkg/profile v1.7.0 - github.com/prometheus/client_golang v1.20.4 + github.com/prometheus/client_golang v1.20.5 github.com/protolambda/ctxlock v0.1.0 - github.com/stretchr/testify v1.9.0 - github.com/urfave/cli/v2 v2.27.4 + github.com/stretchr/testify v1.10.0 + github.com/urfave/cli/v2 v2.27.5 golang.org/x/crypto v0.28.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa - golang.org/x/sync v0.8.0 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c + golang.org/x/sync v0.10.0 golang.org/x/term v0.25.0 golang.org/x/time v0.7.0 - lukechampine.com/uint128 v1.3.0 ) require ( @@ -78,7 +79,7 @@ require ( github.com/consensys/bavard v0.1.13 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect @@ -95,7 +96,7 @@ require ( github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect github.com/fatih/color v1.16.0 // indirect - github.com/felixge/fgprof v0.9.3 // indirect + github.com/felixge/fgprof v0.9.5 // indirect github.com/ferranbt/fastssz v0.1.2 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -111,10 +112,10 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect @@ -234,11 +235,11 @@ require ( go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect @@ -251,7 +252,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum v1.14.11 => github.com/ethereum-optimism/op-geth v1.101408.1-0.20241002211323-d5a96613c22b +replace github.com/ethereum/go-ethereum => github.com/ethstorage/op-geth v0.0.0-20241215102240-7b4b3277d4ad // replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index 5982cfeae1408..865a5fe85cd0f 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -89,12 +91,18 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= @@ -126,8 +134,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= @@ -181,19 +189,22 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101408.1-0.20241002211323-d5a96613c22b h1:9C6WytqAcqWKXQTMw2Da/S/aIJJmMvT+2MUpFnMdGrg= -github.com/ethereum-optimism/op-geth v1.101408.1-0.20241002211323-d5a96613c22b/go.mod h1:7S4pp8KHBmEmKkRjL1BPOc6jY9hW+64YeMUjR3RVLw4= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240910145426-b3905c89e8ac h1:hCIrLuOPV3FJfMDvXeOhCC3uQNvFoMIIlkT2mN2cfeg= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240910145426-b3905c89e8ac/go.mod h1:XaVXL9jg8BcyOeugECgIUGa9Y3DjYJj71RHmb5qon6M= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048 h1:kb220NeqVRRt/XP5JHt3i4zpLsYNCdWMM/0tDnOFk3o= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241126105717-d31591e83048/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A= github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/ethstorage/da-server v0.0.0-20240925084712-169d238000e5 h1:pxtnGOGOChxCETGUg2yEIC3nfB5C7ZWzblm4/JE7108= +github.com/ethstorage/da-server v0.0.0-20240925084712-169d238000e5/go.mod h1:QfuYw2wCpbpW7k+Fwq4s9EMWNu40XDc1KKJICPaNUgQ= +github.com/ethstorage/op-geth v0.0.0-20241215102240-7b4b3277d4ad h1:9EJNrIPvxNHOCloz1hs51ZTGFOw3SCHYmdfI8YHvh5k= +github.com/ethstorage/op-geth v0.0.0-20241215102240-7b4b3277d4ad/go.mod h1:zBADVb3+aon0Idb3uEg/1TFpep+Jdkz3ge9SLFDBXOo= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= +github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= +github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -206,8 +217,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -242,6 +253,9 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -254,8 +268,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -299,8 +313,9 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20241009165004-a3522334989c h1:NDovD0SMpBYXlE1zJmS1q55vWB/fUQBcPAqAboZSccA= +github.com/google/pprof v0.0.0-20241009165004-a3522334989c/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -363,6 +378,7 @@ github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFck github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs= @@ -394,6 +410,7 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -404,8 +421,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= @@ -427,8 +444,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2 h1:izciXrFyFR+ihJ7nLTOkoIX5GzBPIp8gVKlw94gIc98= github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2/go.mod h1:bWSMQK3WHVTGHX9CjxPAb/LtzcmfOxID2wdzakSWQxo= -github.com/kurtosis-tech/kurtosis/api/golang v1.3.0 h1:6r+ER69AgIyoHM48DLcP6r+f6dKDeU+65U8Uiibj46w= -github.com/kurtosis-tech/kurtosis/api/golang v1.3.0/go.mod h1:9T22P7Vv3j5g6sbm78DxHQ4s9C4Cj3s9JjFQ7DFyYpM= +github.com/kurtosis-tech/kurtosis/api/golang v1.4.3 h1:CkrfwpBAOQ9TOCUrVWSv5C7d3hLBNjU4kAYSbL6EHf0= +github.com/kurtosis-tech/kurtosis/api/golang v1.4.3/go.mod h1:9T22P7Vv3j5g6sbm78DxHQ4s9C4Cj3s9JjFQ7DFyYpM= github.com/kurtosis-tech/kurtosis/contexts-config-store v0.0.0-20230818184218-f4e3e773463b h1:hMoIM99QKcYQqsnK4AF7Lovi9ZD9ac6lZLZ5D/jx2x8= github.com/kurtosis-tech/kurtosis/contexts-config-store v0.0.0-20230818184218-f4e3e773463b/go.mod h1:4pFdrRwDz5R+Fov2ZuTaPhAVgjA2jhGh1Izf832sX7A= github.com/kurtosis-tech/kurtosis/grpc-file-transfer/golang v0.0.0-20230803130419-099ee7a4e3dc h1:7IlEpSehmWcNXOFpNP24Cu5HQI3af7GCBQw//m+LnvQ= @@ -443,6 +460,7 @@ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4F github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= @@ -473,6 +491,7 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= @@ -509,8 +528,8 @@ github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4S github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.77 h1:GaGghJRg9nwDVlNbwYjSDJT1rqltQkBFDsypWX1v3Bw= -github.com/minio/minio-go/v7 v7.0.77/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg= +github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA= +github.com/minio/minio-go/v7 v7.0.81/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= @@ -534,10 +553,10 @@ github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYg github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= -github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ= -github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= -github.com/multiformats/go-multiaddr-dns v0.4.0 h1:P76EJ3qzBXpUXZ3twdCDx/kvagMsNo0LMFXpyms/zgU= -github.com/multiformats/go-multiaddr-dns v0.4.0/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= +github.com/multiformats/go-multiaddr v0.14.0 h1:bfrHrJhrRuh/NXH5mCnemjpbGjzRw/b+tJFOD41g2tU= +github.com/multiformats/go-multiaddr v0.14.0/go.mod h1:6EkVAxtznq2yC3QT5CM1UTAwG0GTP3EWAIcjHuzQ+r4= +github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M= +github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= @@ -593,6 +612,7 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= @@ -660,8 +680,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -762,8 +782,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= @@ -782,8 +803,8 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= -github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -846,8 +867,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -860,8 +881,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -894,8 +915,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -911,8 +932,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1021,8 +1042,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1094,8 +1115,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= -lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= -lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= diff --git a/interop-devnet/create-chains.sh b/interop-devnet/create-chains.sh new file mode 100755 index 0000000000000..05e44e67ff21e --- /dev/null +++ b/interop-devnet/create-chains.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +set -eu + +# Run this with workdir set as root of the repo +if [ -f "../mise.toml" ]; then + echo "Running create-chains script." +else + echo "Cannot run create-chains script, must be in interop-devnet dir, but currently in:" + pwd + exit 1 +fi + +# Navigate to repository root +cd .. + +# Check if already created +if [ -d ".devnet-interop" ]; then + echo "Already created chains." + exit 1 +else + echo "Creating new interop devnet chain configs" +fi + +export OP_INTEROP_MNEMONIC="test test test test test test test test test test test junk" + +go run ./op-node/cmd interop dev-setup \ + --artifacts-dir=packages/contracts-bedrock/forge-artifacts \ + --foundry-dir=packages/contracts-bedrock \ + --l1.chainid=900100 \ + --l2.chainids=900200,900201 \ + --out-dir=".devnet-interop" \ + --log.format=logfmt \ + --log.level=info + +# create L1 CL genesis +eth2-testnet-genesis deneb \ + --config=./ops-bedrock/beacon-data/config.yaml \ + --preset-phase0=minimal \ + --preset-altair=minimal \ + --preset-bellatrix=minimal \ + --preset-capella=minimal \ + --preset-deneb=minimal \ + --eth1-config=.devnet-interop/genesis/l1/genesis.json \ + --state-output=.devnet-interop/genesis/l1/beaconstate.ssz \ + --tranches-dir=.devnet-interop/genesis/l1/tranches \ + --mnemonics=./ops-bedrock/mnemonics.yaml \ + --eth1-withdrawal-address=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ + --eth1-match-genesis-time + +echo "Writing env files now..." + +# write env files for each L2 service + +chain_env=".devnet-interop/env/l2/900200" +mkdir -p "$chain_env" +key_cmd="go run ./op-node/cmd interop devkey secret --domain=chain-operator --chainid=900200" +# op-node +echo "OP_NODE_P2P_SEQUENCER_KEY=$($key_cmd --name=sequencer-p2p)" >> "$chain_env/op-node.env" +# proposer +echo "OP_PROPOSER_PRIVATE_KEY=$($key_cmd --name=proposer)" >> "$chain_env/op-proposer.env" +echo "OP_PROPOSER_GAME_FACTORY_ADDRESS=$(jq -r .DisputeGameFactoryProxy .devnet-interop/deployments/l2/900200/addresses.json)" >> "$chain_env/op-proposer.env" +# batcher +echo "OP_BATCHER_PRIVATE_KEY=$($key_cmd --name=batcher)" >> "$chain_env/op-batcher.env" + +chain_env=".devnet-interop/env/l2/900201" +mkdir -p "$chain_env" +key_cmd="go run ./op-node/cmd interop devkey secret --domain=chain-operator --chainid=900201" +# op-node +echo "OP_NODE_P2P_SEQUENCER_KEY=$($key_cmd --name=sequencer-p2p)" >> "$chain_env/op-node.env" +# proposer +echo "OP_PROPOSER_PRIVATE_KEY=$($key_cmd --name=proposer)" >> "$chain_env/op-proposer.env" +echo "OP_PROPOSER_GAME_FACTORY_ADDRESS=$(jq -r .DisputeGameFactoryProxy .devnet-interop/deployments/l2/900201/addresses.json)" >> "$chain_env/op-proposer.env" +# batcher +echo "OP_BATCHER_PRIVATE_KEY=$($key_cmd --name=batcher)" >> "$chain_env/op-batcher.env" + +echo "Interop devnet setup is complete!" diff --git a/interop-devnet/depset.json b/interop-devnet/depset.json new file mode 100644 index 0000000000000..6f3600b1d2965 --- /dev/null +++ b/interop-devnet/depset.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "900200": { + "chainIndex": "900200", + "activationTime": 0, + "historyMinTime": 0 + }, + "900201": { + "chainIndex": "900201", + "activationTime": 0, + "historyMinTime": 0 + } + } +} diff --git a/interop-devnet/docker-compose.yml b/interop-devnet/docker-compose.yml new file mode 100644 index 0000000000000..c4cbab978a122 --- /dev/null +++ b/interop-devnet/docker-compose.yml @@ -0,0 +1,399 @@ +# This Compose file is expected to be used with the devnet-up.sh script. +# The volumes below mount the configs generated by the script into each +# service. + +volumes: + l1_data: + l1_bn_data: + l1_vc_data: + l2_a_data: + safedb_a_data: + l2_b_data: + safedb_b_data: + supervisor_data: + op_log_a: + op_log_b: + +services: + + l1: + build: + context: ../ops-bedrock + dockerfile: l1-geth.Dockerfile + ports: + - "8545:8545" + - "8546:8546" + - "7060:6060" + volumes: + - "l1_data:/db" + - "${PWD}/../.devnet-interop/genesis/l1/genesis.json:/genesis.json" + - "${PWD}/../ops-bedrock/test-jwt-secret.txt:/config/jwt-secret.txt" + environment: + GETH_MINER_RECOMMIT: 100ms + + l1-bn: + depends_on: + - l1 + build: + context: ../ops-bedrock + dockerfile: l1-lighthouse.Dockerfile + ports: + - "9000:9000" + - "5052:5052" + volumes: + - "l1_bn_data:/db" + - "${PWD}/../ops-bedrock/test-jwt-secret.txt:/config/jwt-secret.txt" + - "${PWD}/../ops-bedrock/beacon-data/config.yaml:/genesis/config.yaml" + - "${PWD}/../ops-bedrock/beacon-data/deposit_contract_block.txt:/genesis/deposit_contract_block.txt" + - "${PWD}/../.devnet-interop/genesis/l1/beaconstate.ssz:/genesis/genesis.ssz" + environment: + LH_EXECUTION_ENDPOINT: "http://l1:8551" + entrypoint: + - "/bin/sh" + - "/entrypoint-bn.sh" + + l1-vc: + depends_on: + - l1 + - l1-bn + build: + context: ../ops-bedrock + dockerfile: l1-lighthouse.Dockerfile + volumes: + - "l1_vc_data:/db" + - "${PWD}/../ops-bedrock/beacon-data/data/keys:/validator_setup/validators" + - "${PWD}/../ops-bedrock/beacon-data/data/secrets:/validator_setup/secrets" + - "${PWD}/../ops-bedrock/beacon-data/config.yaml:/genesis/config.yaml" + - "${PWD}/../ops-bedrock/beacon-data/deposit_contract_block.txt:/genesis/deposit_contract_block.txt" + - "${PWD}/../.devnet-interop/genesis/l1/beaconstate.ssz:/genesis/genesis.ssz" + environment: + LH_BEACON_NODES: "http://l1-bn:5052/" + entrypoint: + - "/bin/sh" + - "/entrypoint-vc.sh" + + op-supervisor: + depends_on: + - l1 + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-supervisor-target + ports: + - "9045:8545" + volumes: + - "supervisor_data:/db" + - "./depset.json:/depset.json" + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-supervisor:devnet + command: > + op-supervisor + --datadir="/db" + --dependency-set="/depset.json" + --l2-rpcs="" + --rpc.addr="0.0.0.0" + --rpc.port=8545 + --rpc.enable-admin + --l2-rpcs="ws://l2-a:8546,ws://l2-b:8546" + environment: + OP_SUPERVISOR_METRICS_ENABLED: "true" + + l2-a: + depends_on: + - op-supervisor + build: + context: ../ops-bedrock/ + dockerfile: l2-op-geth-interop.Dockerfile + ports: + - "9145:8545" + - "8160:6060" + volumes: + - "l2_a_data:/db" + - "${PWD}/../.devnet-interop/genesis/l2/900200/genesis.json:/genesis.json" + - "${PWD}/../ops-bedrock/test-jwt-secret.txt:/config/jwt-secret.txt" + entrypoint: # pass the L2 specific flags by overriding the entry-point and adding extra arguments + - "/bin/sh" + - "/entrypoint.sh" + environment: + GETH_MINER_RECOMMIT: 100ms + GETH_ROLLUP_INTEROPRPC: "ws://op-supervisor:8545" + + l2-b: + depends_on: + - op-supervisor + build: + context: ../ops-bedrock/ + dockerfile: l2-op-geth-interop.Dockerfile + ports: + - "9245:8545" + - "8260:6060" + volumes: + - "l2_b_data:/db" + - "${PWD}/../.devnet-interop/genesis/l2/900201/genesis.json:/genesis.json" + - "${PWD}/../ops-bedrock/test-jwt-secret.txt:/config/jwt-secret.txt" + entrypoint: # pass the L2 specific flags by overriding the entry-point and adding extra arguments + - "/bin/sh" + - "/entrypoint.sh" + environment: + GETH_MINER_RECOMMIT: 100ms + GETH_ROLLUP_INTEROPRPC: "ws://op-supervisor:8545" + + op-node-a: + depends_on: + - l1 + - l1-bn + - l1-vc + - l2-a + - op-supervisor + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-node-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:devnet + command: > + op-node + --l1=ws://l1:8546 + --l1.beacon=http://l1-bn:5052 + --l1.epoch-poll-interval=12s + --l1.http-poll-interval=6s + --l2=http://l2-a:8551 + --l2.jwt-secret=/config/jwt-secret.txt + --supervisor=http://op-supervisor:8545 + --sequencer.enabled + --sequencer.l1-confs=0 + --verifier.l1-confs=0 + --rollup.config=/rollup.json + --rpc.addr=0.0.0.0 + --rpc.port=8545 + --p2p.listen.ip=0.0.0.0 + --p2p.listen.tcp=9003 + --p2p.listen.udp=9003 + --p2p.scoring.peers=light + --p2p.ban.peers=true + --metrics.enabled + --metrics.addr=0.0.0.0 + --metrics.port=7300 + --pprof.enabled + --rpc.enable-admin + --safedb.path=/db + ports: + - "7145:8545" + - "9103:9003" + - "7100:7300" + - "6160:6060" + volumes: + - "safedb_a_data:/db" + - "${PWD}/../ops-bedrock/test-jwt-secret.txt:/config/jwt-secret.txt" + - "${PWD}/../.devnet-interop/genesis/l2/900200/rollup.json:/rollup.json" + - op_log_a:/op_log + env_file: + - "${PWD}/../.devnet-interop/env/l2/900200/op-node.env" + + op-node-b: + depends_on: + - l1 + - l1-bn + - l1-vc + - l2-b + - op-supervisor + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-node-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:devnet + command: > + op-node + --l1=ws://l1:8546 + --l1.beacon=http://l1-bn:5052 + --l1.epoch-poll-interval=12s + --l1.http-poll-interval=6s + --l2=http://l2-b:8551 + --l2.jwt-secret=/config/jwt-secret.txt + --supervisor=http://op-supervisor:8545 + --sequencer.enabled + --sequencer.l1-confs=0 + --verifier.l1-confs=0 + --rollup.config=/rollup.json + --rpc.addr=0.0.0.0 + --rpc.port=8545 + --p2p.listen.ip=0.0.0.0 + --p2p.listen.tcp=9003 + --p2p.listen.udp=9003 + --p2p.scoring.peers=light + --p2p.ban.peers=true + --metrics.enabled + --metrics.addr=0.0.0.0 + --metrics.port=7300 + --pprof.enabled + --rpc.enable-admin + --safedb.path=/db + ports: + - "7245:8545" + - "9203:9003" + - "7200:7300" + - "6260:6060" + volumes: + - "safedb_b_data:/db" + - "${PWD}/../ops-bedrock/test-jwt-secret.txt:/config/jwt-secret.txt" + - "${PWD}/../.devnet-interop/genesis/l2/900201/rollup.json:/rollup.json" + - op_log_b:/op_log + env_file: + - "${PWD}/../.devnet-interop/env/l2/900201/op-node.env" + + op-proposer-a: + depends_on: + - l1 + - op-node-a + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-proposer-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:devnet + ports: + - "6162:6060" + - "7102:7300" + - "6146:8545" + environment: + OP_PROPOSER_L1_ETH_RPC: http://l1:8545 + OP_PROPOSER_ROLLUP_RPC: http://op-node-a:8545 + OP_PROPOSER_POLL_INTERVAL: 1s + OP_PROPOSER_NUM_CONFIRMATIONS: 1 + OP_PROPOSER_GAME_TYPE: "254" + OP_PROPOSER_PROPOSAL_INTERVAL: "12s" + OP_PROPOSER_PPROF_ENABLED: "true" + OP_PROPOSER_METRICS_ENABLED: "true" + OP_PROPOSER_ALLOW_NON_FINALIZED: "true" + OP_PROPOSER_RPC_ENABLE_ADMIN: "true" + env_file: + - "${PWD}/../.devnet-interop/env/l2/900200/op-proposer.env" + + op-proposer-b: + depends_on: + - l1 + - op-node-b + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-proposer-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:devnet + ports: + - "6262:6060" + - "7202:7300" + - "6246:8545" + environment: + OP_PROPOSER_L1_ETH_RPC: http://l1:8545 + OP_PROPOSER_ROLLUP_RPC: http://op-node-b:8545 + OP_PROPOSER_POLL_INTERVAL: 1s + OP_PROPOSER_NUM_CONFIRMATIONS: 1 + OP_PROPOSER_GAME_TYPE: "254" + OP_PROPOSER_PROPOSAL_INTERVAL: "12s" + OP_PROPOSER_PPROF_ENABLED: "true" + OP_PROPOSER_METRICS_ENABLED: "true" + OP_PROPOSER_ALLOW_NON_FINALIZED: "true" + OP_PROPOSER_RPC_ENABLE_ADMIN: "true" + env_file: + - "${PWD}/../.devnet-interop/env/l2/900201/op-proposer.env" + + op-batcher-a: + depends_on: + - l1 + - l2-a + - op-node-a + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-batcher-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:devnet + ports: + - "6161:6060" + - "7101:7300" + - "6145:8545" + environment: + OP_BATCHER_L1_ETH_RPC: http://l1:8545 + OP_BATCHER_L2_ETH_RPC: http://l2-a:8545 + OP_BATCHER_ROLLUP_RPC: http://op-node-a:8545 + OP_BATCHER_MAX_CHANNEL_DURATION: 2 + OP_BATCHER_SUB_SAFETY_MARGIN: 4 # SWS is 15, ChannelTimeout is 40 + OP_BATCHER_POLL_INTERVAL: 1s + OP_BATCHER_NUM_CONFIRMATIONS: 1 + OP_BATCHER_PPROF_ENABLED: "true" + OP_BATCHER_METRICS_ENABLED: "true" + OP_BATCHER_RPC_ENABLE_ADMIN: "true" + OP_BATCHER_BATCH_TYPE: + OP_BATCHER_THROTTLE_INTERVAL: 0 + # uncomment to use blobs + # OP_BATCHER_DATA_AVAILABILITY_TYPE: blobs + env_file: + - "${PWD}/../.devnet-interop/env/l2/900200/op-batcher.env" + + op-batcher-b: + depends_on: + - l1 + - l2-b + - op-node-b + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-batcher-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:devnet + ports: + - "6261:6060" + - "7201:7300" + - "6245:8545" + environment: + OP_BATCHER_L1_ETH_RPC: http://l1:8545 + OP_BATCHER_L2_ETH_RPC: http://l2-b:8545 + OP_BATCHER_ROLLUP_RPC: http://op-node-b:8545 + OP_BATCHER_MAX_CHANNEL_DURATION: 2 + OP_BATCHER_SUB_SAFETY_MARGIN: 4 # SWS is 15, ChannelTimeout is 40 + OP_BATCHER_POLL_INTERVAL: 1s + OP_BATCHER_NUM_CONFIRMATIONS: 1 + OP_BATCHER_PPROF_ENABLED: "true" + OP_BATCHER_METRICS_ENABLED: "true" + OP_BATCHER_RPC_ENABLE_ADMIN: "true" + OP_BATCHER_BATCH_TYPE: + OP_BATCHER_THROTTLE_INTERVAL: 0 + # uncomment to use blobs + # OP_BATCHER_DATA_AVAILABILITY_TYPE: blobs + env_file: + - "${PWD}/../.devnet-interop/env/l2/900201/op-batcher.env" + + grafana: + image: grafana/grafana:11.1.0 + restart: unless-stopped + env_file: + - monitoring/grafana.env + volumes: + - ./monitoring/grafana/provisioning/:/etc/grafana/provisioning/:ro + - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards + # - grafana_data:/var/lib/grafana + ports: + - 3300:3000 + + prometheus: + image: prom/prometheus:latest + restart: unless-stopped + volumes: + - ./monitoring/prometheus:/etc/prometheus + # - prometheus_data:/prometheus + ports: + - 3090:9090 + command: --config.file=/etc/prometheus/prometheus.yml --log.level=debug + + loki: + image: grafana/loki:3.1.1 + restart: unless-stopped + volumes: + - ./monitoring/loki:/etc/loki + ports: + - 3200:3200 + command: -config.file=/etc/loki/config.yaml + + promtail: + image: grafana/promtail:3.1.1 + restart: unless-stopped + volumes: + # uncomment to scrape system logs + # - /var/log:/var/log + - ./monitoring/promtail:/etc/promtail + - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket to read container logs + command: -config.file=/etc/promtail/config.yaml diff --git a/interop-devnet/justfile b/interop-devnet/justfile new file mode 100644 index 0000000000000..6af11ccc8fb8c --- /dev/null +++ b/interop-devnet/justfile @@ -0,0 +1,40 @@ + +devnet-setup: + bash create-chains.sh + +devnet-build-images: + PWD="$(pwd)" DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 \ + docker compose build --progress plain \ + --build-arg GIT_COMMIT={git_commit} \ + --build-arg GIT_DATE={git_date} + +devnet-up: + docker compose up -d l1 l1-bn l1-vc + + docker compose up -d \ + op-supervisor \ + op-node-a op-batcher-a op-proposer-a \ + op-node-b op-batcher-b op-proposer-b + +devnet-down: + # stops services, does not remove containers/networks + docker compose stop + +devnet-metrics-up: + docker compose up -d prometheus grafana loki promtail + +devnet-metrics-down: + docker compose down -d prometheus grafana loki promtail + +devnet-clean: + # Stops services, and removes containers/networks + docker compose down + # Now manually clean up the related images and volumes + # Note: `justfile` interprets the curly brackets. So we escape them, by wrapping it with more, as a string, like Jinja2. + docker image ls 'interop-devnet*' --format='{{ '{{.Repository}}' }}' | xargs -r docker rmi + docker volume ls --filter name=interop-devnet --format='{{ '{{.Name}}' }}' | xargs -r docker volume rm + # docker compose down needs the env files before being able to shut down, so remove the devnet config resources last + rm -rf ../.devnet-interop + +devnet-logs: + docker compose logs -f diff --git a/interop-devnet/monitoring/grafana.env b/interop-devnet/monitoring/grafana.env new file mode 100644 index 0000000000000..3a7446dfccfaf --- /dev/null +++ b/interop-devnet/monitoring/grafana.env @@ -0,0 +1 @@ +GF_SECURITY_ADMIN_PASSWORD=admin diff --git a/interop-devnet/monitoring/grafana/dashboards/op_stack_compose_dashboard.json b/interop-devnet/monitoring/grafana/dashboards/op_stack_compose_dashboard.json new file mode 100644 index 0000000000000..c3d34c9d8a56d --- /dev/null +++ b/interop-devnet/monitoring/grafana/dashboards/op_stack_compose_dashboard.json @@ -0,0 +1,2203 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 31, + "panels": [], + "title": "Logs", + "type": "row" + }, + { + "datasource": { + "type": "loki", + "uid": "loki-datasource" + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 24, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki-datasource" + }, + "expr": "{container=\"ops-bedrock-op-batcher-1\"}", + "queryType": "range", + "refId": "A" + } + ], + "title": "Batcher Logs", + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "loki-datasource" + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 32, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki-datasource" + }, + "editorMode": "code", + "expr": "{container=\"ops-bedrock-op-proposer-1\"}", + "queryType": "range", + "refId": "A" + } + ], + "title": "Proposer Logs", + "type": "logs" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 14, + "panels": [], + "title": "Batcher", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 22 + }, + "id": 20, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_pending_blocks_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "pending_blocks_count {{stage}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Number of pending blocks, not added to a channel yet.", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 22 + }, + "id": 19, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_input_bytes", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "input {{stage}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_output_bytes", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "output", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Total number of input/output bytes per channel.", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 30 + }, + "id": 17, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_pending_blocks_bytes_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "op_batcher_default_pending_blocks_bytes_current", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Total size of transactions in pending blocks as they are fetched from L2", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 30 + }, + "id": 16, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_blocks_added_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total number of blocks added to current channel.", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 38 + }, + "id": 18, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_channel_num_frames", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total number of frames of closed channel.", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 38 + }, + "id": 25, + "interval": "2s", + "options": { + "displayMode": "basic", + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_blob_used_bytes_bucket", + "format": "heatmap", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Blob sizes", + "type": "bargauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 50 + }, + "id": 26, + "panels": [], + "title": "Batcher txmgr", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 51 + }, + "id": 21, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_txmgr_last_confirm_unix", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_txmgr_last_publish_unix", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Batcher Tx Publish/Confirm Times", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "description": "TODO: batcher_tx_total is 2x txs sent b/c of bug. See https://github.com/ethereum-optimism/optimism/pull/11438", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 51 + }, + "id": 15, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_batcher_tx_total", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_txmgr_confirm_total", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "B", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_txmgr_current_nonce", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "C", + "useBackend": false + } + ], + "title": "Batcher Txs", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 59 + }, + "id": 23, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_txmgr_pending_txs", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batcher Pending Txs", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 59 + }, + "id": 22, + "interval": "2s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_txmgr_tx_confirmed_latency_ms", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{__name__}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Tx confirmation latency", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 67 + }, + "id": 6, + "panels": [], + "title": "Sequencer", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 68 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_node_default_refs_number{type=\"l2_unsafe\", layer=\"l2\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "L2 Unsafe Block", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 5, + "y": 68 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_node_default_refs_number{type=\"l2_safe\", layer=\"l2\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "L2 Safe Block", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 10, + "y": 68 + }, + "id": 9, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_node_default_refs_number{type=\"l2_finalized\", layer=\"l2\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "L2 Finalized Block", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "A" + }, + "properties": [ + { + "id": "displayName", + "value": "Balance" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 68 + }, + "id": 13, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hoverProximity": -46, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(op_node_default_l1_reorg_depth_count[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "L1 Reorg Count", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 72 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_node_default_refs_number{type=\"l1_head\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "L1 Head", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 5, + "y": 72 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_node_default_refs_number{type=\"l1_safe\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "L1 Safe Block", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 10, + "y": 72 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_node_default_refs_number{type=\"l1_finalized\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "L1 Finalized Block", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 76 + }, + "id": 5, + "panels": [], + "title": "Balances", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 4, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.5 + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 77 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_balance", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batcher Balance", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 4, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 80 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": { + "valueSize": 54 + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_proposer_default_balance", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Proposer Balance", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "A" + }, + "properties": [ + { + "id": "displayName", + "value": "Balance" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 83 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hoverProximity": -46, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_batcher_default_balance", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Batcher Balance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "A" + }, + "properties": [ + { + "id": "displayName", + "value": "Balance" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 83 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "ddshms3dlineoe" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "op_proposer_default_balance", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Proposer Balance", + "type": "timeseries" + } + ], + "refresh": "10s", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "OP Stack Compose", + "uid": "ddshn0bgt86ioc", + "version": 1, + "weekStart": "" +} diff --git a/interop-devnet/monitoring/grafana/provisioning/dashboards/all.yml b/interop-devnet/monitoring/grafana/provisioning/dashboards/all.yml new file mode 100644 index 0000000000000..e281a1e592c8c --- /dev/null +++ b/interop-devnet/monitoring/grafana/provisioning/dashboards/all.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: true + editable: true + options: + path: /var/lib/grafana/dashboards diff --git a/interop-devnet/monitoring/grafana/provisioning/datasources/all.yml b/interop-devnet/monitoring/grafana/provisioning/datasources/all.yml new file mode 100644 index 0000000000000..8a97e63640d4a --- /dev/null +++ b/interop-devnet/monitoring/grafana/provisioning/datasources/all.yml @@ -0,0 +1,24 @@ +apiVersion: 1 + +deleteDatasources: + - name: "Prometheus" + +datasources: + - access: "proxy" + editable: true + is_default: true + name: "Prometheus" + uid: "ddshms3dlineoe" + org_id: 1 + type: "prometheus" + url: "http://prometheus:9090" + version: 1 + + - access: "proxy" + editable: true + name: "Loki" + uid: "loki-datasource" + org_id: 1 + type: "loki" + url: "http://loki:3200" + version: 1 diff --git a/interop-devnet/monitoring/loki/config.yaml b/interop-devnet/monitoring/loki/config.yaml new file mode 100644 index 0000000000000..f5350cfc2444f --- /dev/null +++ b/interop-devnet/monitoring/loki/config.yaml @@ -0,0 +1,29 @@ +auth_enabled: false + +server: + http_listen_port: 3200 + +common: + instance_addr: 127.0.0.1 + path_prefix: /loki + storage: + filesystem: + chunks_directory: /loki/chunks + rules_directory: /loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +analytics: + reporting_enabled: false diff --git a/interop-devnet/monitoring/prometheus/prometheus.yml b/interop-devnet/monitoring/prometheus/prometheus.yml new file mode 100644 index 0000000000000..c2a7e6fe3a628 --- /dev/null +++ b/interop-devnet/monitoring/prometheus/prometheus.yml @@ -0,0 +1,43 @@ +global: + scrape_interval: 4s + evaluation_interval: 15s + +scrape_configs: + - job_name: "l1-geth" + static_configs: + - targets: ["l1:6060"] + metrics_path: /debug/metrics/prometheus + + - job_name: "l2-a-geth" + static_configs: + - targets: ["l2-a:6060"] + metrics_path: /debug/metrics/prometheus + - job_name: "l2-b-geth" + static_configs: + - targets: ["l2-b:6060"] + metrics_path: /debug/metrics/prometheus + + - job_name: "op-node-a" + static_configs: + - targets: ["op-node-a:7300"] + - job_name: "op-node-b" + static_configs: + - targets: ["op-node-b:7300"] + + - job_name: "op-batcher-a" + static_configs: + - targets: ["op-batcher-a:7300"] + - job_name: "op-batcher-b" + static_configs: + - targets: ["op-batcher-b:7300"] + + - job_name: "op-proposer-a" + static_configs: + - targets: ["op-proposer-a:7300"] + - job_name: "op-proposer-b" + static_configs: + - targets: ["op-proposer-b:7300"] + + - job_name: "op-supervisor" + static_configs: + - targets: ["op-supervisor:7300"] diff --git a/interop-devnet/monitoring/promtail/config.yaml b/interop-devnet/monitoring/promtail/config.yaml new file mode 100644 index 0000000000000..60116fb463f62 --- /dev/null +++ b/interop-devnet/monitoring/promtail/config.yaml @@ -0,0 +1,32 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://loki:3200/loki/api/v1/push + +scrape_configs: + # Uncomment to scrape system logs + # - job_name: system + # static_configs: + # - targets: + # - localhost + # labels: + # job: varlogs + # __path__: /var/log/*log + # This scrapes docker container logs + # copied from https://stackoverflow.com/questions/74776432/with-promtail-how-do-i-only-keep-log-messages-for-specified-docker-containers + - job_name: docker + docker_sd_configs: + - host: unix:///var/run/docker.sock + filters: + - name: name + # Filter logging to just our containers + values: ["op-batcher-*", "op-proposer-*", "op-node-*", "op-supervisor-*", "l1-*", "l1-bn-*", "l1-vc-*", "l2-a-*", "l2-b-*"] + relabel_configs: + - source_labels: ["__meta_docker_container_name"] + regex: "/(.*)" + target_label: "container" diff --git a/just/default.just b/just/default.just new file mode 100644 index 0000000000000..92503b23ba58a --- /dev/null +++ b/just/default.just @@ -0,0 +1,6 @@ +set shell := ["bash", "-c"] + +PARALLEL_JOBS := num_cpus() + +# TODO: this fails in CI for some reason +MAP_JUST := "/usr/bin/env -S parallel --shebang --jobs " + PARALLEL_JOBS + " --colsep ' ' -r " + just_executable() diff --git a/just/deprecated.mk b/just/deprecated.mk new file mode 100644 index 0000000000000..655f901348d64 --- /dev/null +++ b/just/deprecated.mk @@ -0,0 +1,27 @@ +ifeq (, $(shell which tput)) + # CI environment typically does not support tput. + banner-style = $1 +else ifeq (, $(TERM)) + # Terminal type not set, so tput would fail. + banner-style = $1 +else + # print in bold red to bring attention. + banner-style = $(shell tput bold)$(shell tput setaf 1)$1$(shell tput sgr0) +endif + +SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(SELF_DIR)/flags.mk + +define make-deprecated-target +$1: + @echo + @printf %s\\n '$(call banner-style,Deprecated make call: make $1 $(JUSTFLAGS))' + @printf %s\\n '$(call banner-style,Consider using just instead: just $(JUSTFLAGS) $1)' + @echo + just $(JUSTFLAGS) $1 +endef + +$(foreach element,$(DEPRECATED_TARGETS),$(eval $(call make-deprecated-target,$(element)))) + +.PHONY: + $(DEPRECATED_TARGETS) diff --git a/just/flags.mk b/just/flags.mk new file mode 100644 index 0000000000000..121a3eb70e988 --- /dev/null +++ b/just/flags.mk @@ -0,0 +1,24 @@ +# Variable assignments can affect the semantic of the make targets. +# Typical use-case: setting VERSION in a release build, since CI +# doesn't preserve the git environment. +# +# We need to translate: +# "make target VAR=val" to "just VAR=val target" +# +# MAKEFLAGS is a string of the form: +# "abc --foo --bar=baz -- VAR1=val1 VAR2=val2", namely: +# - abc is the concatnation of all short flags +# - --foo and --bar=baz are long options, +# - -- is the separator between flags and variable assignments, +# - VAR1=val1 and VAR2=val2 are variable assignments +# +# Goal: ignore all CLI flags, keep only variable assignments. +# +# First remove the short flags at the beginning, or the first long-flag, +# or if there is no flag at all, the -- separator (which then makes the +# next step a noop). If there's no flag and no variable assignment, the +# result is empty anyway, so the wordlist call is safe (everything is a noop). +tmp-flags := $(wordlist 2,$(words $(MAKEFLAGS)),$(MAKEFLAGS)) +# Then remove all long options, including the -- separator, if needed. That +# leaves only variable assignments. +JUSTFLAGS := $(patsubst --%,,$(tmp-flags)) \ No newline at end of file diff --git a/just/git.just b/just/git.just new file mode 100644 index 0000000000000..ac50e4e762d14 --- /dev/null +++ b/just/git.just @@ -0,0 +1,28 @@ +import 'default.just' + +# Set default values for git info +GITCOMMIT := env('GITCOMMIT', `git rev-parse HEAD 2> /dev/null || true`) +GITDATE := env('GITDATE', `git show -s --format='%ct' 2> /dev/null|| true`) + +_PROJECT := shell("basename $1", justfile_directory()) + +_ALL_TAGS := shell("git tag --points-at $1 2> /dev/null || true", GITCOMMIT) + +_PROJECT_TAGS := shell("echo $1 | grep ^$2/ | sed s:$2/:: | sort -V", _ALL_TAGS, _PROJECT) + +_PREFERRED_TAG := shell("echo $1 | grep -v -- '-rc' | tail -n 1", _PROJECT_TAGS) + +_LAST_TAG := shell("echo $1 | tail -n 1", _PROJECT_TAGS) + +# Find version tag, prioritizing non-rc release tags +VERSION := shell('if [ -z "$1" ]; then + if [ -z "$2" ]; then + echo "untagged" + else + echo "$2" + fi +else + echo $1 +fi', _PREFERRED_TAG, _LAST_TAG) + +VERSION_META := "" diff --git a/just/go.just b/just/go.just new file mode 100644 index 0000000000000..3a394e3ec94c5 --- /dev/null +++ b/just/go.just @@ -0,0 +1,31 @@ +import 'git.just' + +_EXTRALDFLAGS := if os() == "macos" { "-ldflags=-extldflags=-Wl,-ld_classic" } else { "" } + +# We use both GOOS/GOARCH and TARGETOS/TARGETARCH to set the build targets. +# From the usage patterns, it looks like TARGETOS/TARGETARCH should take +# precedence if set, and default to GOOS/GOARCH if not set. +# TODO: should we just remove TARGETOS/TARGETARCH altogether eventually? +GOOS := env('GOOS', `go env GOOS`) +GOARCH := env('GOARCH', `go env GOARCH`) +TARGETOS := env('TARGETOS', GOOS) +TARGETARCH := env('TARGETARCH', GOARCH) + +GORACE := "0" + +_GORACE_FLAG := if GORACE == "1" { "-race " } else { "" } + +[private] +go_build BIN PKG *FLAGS: + env GO111MODULE=on GOOS={{TARGETOS}} GOARCH={{TARGETARCH}} CGO_ENABLED=0 go build -v {{_GORACE_FLAG}} {{FLAGS}} -o {{BIN}} {{PKG}} + +[private] +go_test SELECTOR *FLAGS: + go test -v {{_GORACE_FLAG}} {{FLAGS}} {{SELECTOR}} + +[private] +go_fuzz FUZZ TIME='10s' PKG='': (go_test PKG _EXTRALDFLAGS "-fuzztime" TIME "-fuzz" FUZZ "-run" "NOTAREALTEST") + +[private] +go_generate SELECTOR *FLAGS: + go generate -v {{FLAGS}} {{SELECTOR}} \ No newline at end of file diff --git a/justfile b/justfile index 1ce2b2408afb9..35900f2a50bf3 100644 --- a/justfile +++ b/justfile @@ -1,60 +1,15 @@ -issues: +# Checks that TODO comments have corresponding issues. +todo-checker: ./ops/scripts/todo-checker.sh # Runs semgrep on the entire monorepo. semgrep: - semgrep scan --config=.semgrep --error . + semgrep scan --config .semgrep/rules/ --error . -lint-shellcheck: - find . -type f -name '*.sh' -not -path '*/node_modules/*' -not -path './packages/contracts-bedrock/lib/*' -not -path './packages/contracts-bedrock/kout*/*' -exec sh -c 'echo \"Checking $1\"; shellcheck \"$1\"' _ {} \\; +# Runs semgrep tests. +semgrep-test: + semgrep scan --test --config .semgrep/rules/ .semgrep/tests/ -install-foundry: - curl -L https://foundry.paradigm.xyz | bash && just update-foundry - -update-foundry: - bash ./ops/scripts/install-foundry.sh - -check-foundry: - bash ./packages/contracts-bedrock/scripts/checks/check-foundry-install.sh - -install-kontrol: - curl -L https://kframework.org/install | bash && just update-kontrol - -update-kontrol: - kup install kontrol --version v$(jq -r .kontrol < versions.json) - -install-abigen: - go install github.com/ethereum/go-ethereum/cmd/abigen@$(jq -r .abigen < versions.json) - -print-abigen: - abigen --version | sed -e 's/[^0-9]/ /g' -e 's/^ *//g' -e 's/ *$//g' -e 's/ /./g' -e 's/^/v/' - -check-abigen: - [[ $(just print-abigen) = $(cat versions.json | jq -r '.abigen') ]] && echo '✓ abigen versions match' || (echo '✗ abigen version mismatch. Run `just upgrade:abigen` to upgrade.' && exit 1) - -upgrade-abigen: - jq '.abigen = $v' --arg v $(just print:abigen) <<<$(cat versions.json) > versions.json - -install-slither: - pip3 install slither-analyzer==$(jq -r .slither < versions.json) - -print-slither: - slither --version - -check-slither: - [[ $(just print-slither) = $(jq -r .slither < versions.json) ]] && echo '✓ slither versions match' || (echo '✗ slither version mismatch. Run `just upgrade-slither` to upgrade.' && exit 1) - -upgrade-slither: - jq '.slither = $v' --arg v $(just print-slither) <<<$(cat versions.json) > versions.json - -install-semgrep: - pip3 install semgrep - -print-semgrep: - semgrep --version - -check-semgrep: - [ "$(just print-semgrep)" = "$(jq -r .semgrep < versions.json)" ] && echo '✓ semgrep versions match' || (echo '✗ semgrep version mismatch. Run `just upgrade-semgrep` to upgrade.' && exit 1) - -upgrade-semgrep: - jq '.semgrep = $v' --arg v $(just print-semgrep) <<<$(cat versions.json) > versions.json +# Runs shellcheck. +shellcheck: + find . -type f -name '*.sh' -not -path '*/node_modules/*' -not -path './packages/contracts-bedrock/lib/*' -not -path './packages/contracts-bedrock/kout*/*' -exec sh -c 'echo "Checking $1"; shellcheck "$1"' _ {} \; diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000000000..5fb473de8891b --- /dev/null +++ b/mise.toml @@ -0,0 +1,54 @@ +[tools] + +# Core dependencies +go = "1.22.7" +rust = "1.83.0" +python = "3.12.0" +uv = "0.5.5" +jq = "1.7.1" +yq = "4.44.5" +shellcheck = "0.10.0" +direnv = "2.35.0" +just = "1.37.0" + +# Cargo dependencies +"cargo:just" = "1.37.0" +"cargo:svm-rs" = "0.5.8" + +# Go dependencies +"go:github.com/ethereum/go-ethereum/cmd/abigen" = "1.10.25" +"go:github.com/ethereum/go-ethereum/cmd/geth" = "1.14.7" +"go:github.com/protolambda/eth2-testnet-genesis" = "0.10.0" +"go:gotest.tools/gotestsum" = "1.12.0" +"go:github.com/vektra/mockery/v2" = "2.46.0" +"go:github.com/golangci/golangci-lint/cmd/golangci-lint" = "1.61.0" +"go:github.com/mikefarah/yq/v4" = "4.44.3" + +# Python dependencies +"pipx:slither-analyzer" = "0.10.2" +"pipx:semgrep" = "1.90.0" + +# Foundry dependencies +# Foundry is a special case because it supplies multiple binaries at the same +# GitHub release, so we need to use the aliasing trick to get mise to not error +forge = "nightly-e5dbb7a320c2b871c4a4a1006ad3c15a08fcf17b" +cast = "nightly-e5dbb7a320c2b871c4a4a1006ad3c15a08fcf17b" +anvil = "nightly-e5dbb7a320c2b871c4a4a1006ad3c15a08fcf17b" + +# Fake dependencies +# Put things here if you need to track versions of tools or projects that can't +# actually be managed by mise (yet). Make sure that anything you put in here is +# also found inside of disabled_tools or mise will try to install it. +asterisc = "1.2.0" +kontrol = "1.0.53" +binary_signer = "1.0.4" + +[alias] +forge = "ubi:foundry-rs/foundry[exe=forge]" +cast = "ubi:foundry-rs/foundry[exe=cast]" +anvil = "ubi:foundry-rs/foundry[exe=anvil]" + +[settings] +experimental = true +pipx.uvx = true +disable_tools = ["asterisc", "kontrol", "binary_signer"] diff --git a/op-alt-da/Makefile b/op-alt-da/Makefile index c98ea24c2095c..eb6fb9b78043f 100644 --- a/op-alt-da/Makefile +++ b/op-alt-da/Makefile @@ -1,22 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -VERSION ?= v0.0.0 +DEPRECATED_TARGETS := da-server clean test -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.Version=$(VERSION) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -da-server: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/da-server ./cmd/daserver - -clean: - rm bin/da-server - -test: - go test -v ./... - -.PHONY: \ - op-batcher \ - clean \ - test +include ../just/deprecated.mk diff --git a/op-alt-da/cli.go b/op-alt-da/cli.go index 30ce2168f5702..84364e47952a7 100644 --- a/op-alt-da/cli.go +++ b/op-alt-da/cli.go @@ -57,22 +57,25 @@ func CLIFlags(envPrefix string, category string) []cli.Flag { Category: category, }, &cli.DurationFlag{ - Name: PutTimeoutFlagName, - Usage: "Timeout for put requests. 0 means no timeout.", - Value: time.Duration(0), - EnvVars: altDAEnvs(envPrefix, "PUT_TIMEOUT"), + Name: PutTimeoutFlagName, + Usage: "Timeout for put requests. 0 means no timeout.", + Value: time.Duration(0), + EnvVars: altDAEnvs(envPrefix, "PUT_TIMEOUT"), + Category: category, }, &cli.DurationFlag{ - Name: GetTimeoutFlagName, - Usage: "Timeout for get requests. 0 means no timeout.", - Value: time.Duration(0), - EnvVars: altDAEnvs(envPrefix, "GET_TIMEOUT"), + Name: GetTimeoutFlagName, + Usage: "Timeout for get requests. 0 means no timeout.", + Value: time.Duration(0), + EnvVars: altDAEnvs(envPrefix, "GET_TIMEOUT"), + Category: category, }, &cli.Uint64Flag{ - Name: MaxConcurrentRequestsFlagName, - Usage: "Maximum number of concurrent requests to the DA server", - Value: 1, - EnvVars: altDAEnvs(envPrefix, "MAX_CONCURRENT_DA_REQUESTS"), + Name: MaxConcurrentRequestsFlagName, + Usage: "Maximum number of concurrent requests to the DA server", + Value: 1, + EnvVars: altDAEnvs(envPrefix, "MAX_CONCURRENT_DA_REQUESTS"), + Category: category, }, } } diff --git a/op-alt-da/cmd/daserver/main.go b/op-alt-da/cmd/daserver/main.go index 3ed37bd053218..bcaa9c47280eb 100644 --- a/op-alt-da/cmd/daserver/main.go +++ b/op-alt-da/cmd/daserver/main.go @@ -13,7 +13,7 @@ import ( oplog "github.com/ethereum-optimism/optimism/op-service/log" ) -var Version = "v0.0.1" +var Version = "v0.0.0" func main() { oplog.SetupDefaults() diff --git a/op-alt-da/commitment.go b/op-alt-da/commitment.go index cc5829ad4dc64..a6fa5424665b1 100644 --- a/op-alt-da/commitment.go +++ b/op-alt-da/commitment.go @@ -108,7 +108,7 @@ func (c Keccak256Commitment) CommitmentType() CommitmentType { return Keccak256CommitmentType } -// Encode adds a commitment type prefix self describing the commitment. +// Encode adds a commitment type prefix that describes the commitment. func (c Keccak256Commitment) Encode() []byte { return append([]byte{byte(Keccak256CommitmentType)}, c...) } diff --git a/op-alt-da/justfile b/op-alt-da/justfile new file mode 100644 index 0000000000000..f1dbced35b696 --- /dev/null +++ b/op-alt-da/justfile @@ -0,0 +1,20 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X main.Version=" + VERSION + " " + \ + "") + "'" + +BINARY := "./bin/da-server" + +# Build the da-server binary +da-server: (go_build BINARY "./cmd/daserver" "-ldflags" _LDFLAGSSTRING) + +# Remove build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") diff --git a/op-batcher/Makefile b/op-batcher/Makefile index 8e680ec2bec12..1501e0242d9b8 100644 --- a/op-batcher/Makefile +++ b/op-batcher/Makefile @@ -1,50 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -# Find the github tag that points to this commit. If none are found, set the version string to "untagged" -# Prioritizes release tag, if one exists, over tags suffixed with "-rc" -VERSION ?= $(shell tags=$$(git tag --points-at $(GITCOMMIT) | grep '^op-batcher/' | sed 's/op-batcher\///' | sort -V); \ - preferred_tag=$$(echo "$$tags" | grep -v -- '-rc' | tail -n 1); \ - if [ -z "$$preferred_tag" ]; then \ - if [ -z "$$tags" ]; then \ - echo "untagged"; \ - else \ - echo "$$tags" | tail -n 1; \ - fi \ - else \ - echo $$preferred_tag; \ - fi) +DEPRECATED_TARGETS := op-batcher clean test fuzz -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.Version=$(VERSION) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -# Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169 -ifeq ($(shell uname),Darwin) - FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic -endif - -op-batcher: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-batcher ./cmd - -clean: - rm bin/op-batcher - -test: - go test -v ./... - -fuzz: - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelConfig_CheckTimeout ./batcher - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationZero ./batcher - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationTimeoutMaxChannelDuration ./batcher - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationTimeoutZeroMaxChannelDuration ./batcher - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelCloseTimeout ./batcher - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelZeroCloseTimeout ./batcher - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzSeqWindowClose ./batcher - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzSeqWindowZeroTimeoutClose ./batcher - -.PHONY: \ - op-batcher \ - clean \ - test \ - fuzz +include ../just/deprecated.mk diff --git a/op-batcher/architecture.png b/op-batcher/architecture.png new file mode 100644 index 0000000000000..0eab940fbb522 Binary files /dev/null and b/op-batcher/architecture.png differ diff --git a/op-batcher/batcher/channel.go b/op-batcher/batcher/channel.go index de68fa588a0a6..95abcb46a7fae 100644 --- a/op-batcher/batcher/channel.go +++ b/op-batcher/batcher/channel.go @@ -1,7 +1,6 @@ package batcher import ( - "fmt" "math" "github.com/ethereum-optimism/optimism/op-batcher/metrics" @@ -26,20 +25,14 @@ type channel struct { // Set of confirmed txID -> inclusion block. For determining if the channel is timed out confirmedTransactions map[string]eth.BlockID - // True if confirmed TX list is updated. Set to false after updated min/max inclusion blocks. - confirmedTxUpdated bool // Inclusion block number of first confirmed TX minInclusionBlock uint64 // Inclusion block number of last confirmed TX maxInclusionBlock uint64 } -func newChannel(log log.Logger, metr metrics.Metricer, cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64) (*channel, error) { - cb, err := NewChannelBuilder(cfg, rollupCfg, latestL1OriginBlockNum) - if err != nil { - return nil, fmt.Errorf("creating new channel: %w", err) - } - +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) return &channel{ log: log, metr: metr, @@ -47,112 +40,88 @@ func newChannel(log log.Logger, metr metrics.Metricer, cfg ChannelConfig, rollup channelBuilder: cb, pendingTransactions: make(map[string]txData), confirmedTransactions: make(map[string]eth.BlockID), - }, nil + minInclusionBlock: math.MaxUint64, + } } // TxFailed records a transaction as failed. It will attempt to resubmit the data // in the failed transaction. -func (s *channel) TxFailed(id string) { - if data, ok := s.pendingTransactions[id]; ok { - s.log.Trace("marked transaction as failed", "id", id) - // Note: when the batcher is changed to send multiple frames per tx, - // this needs to be changed to iterate over all frames of the tx data - // and re-queue them. - s.channelBuilder.PushFrames(data.Frames()...) - delete(s.pendingTransactions, id) +func (c *channel) TxFailed(id string) { + if data, ok := c.pendingTransactions[id]; ok { + c.log.Trace("marked transaction as failed", "id", id) + // Rewind to the first frame of the failed tx + // -- the frames are ordered, and we want to send them + // all again. + c.channelBuilder.RewindFrameCursor(data.Frames()[0]) + delete(c.pendingTransactions, id) } else { - s.log.Warn("unknown transaction marked as failed", "id", id) + c.log.Warn("unknown transaction marked as failed", "id", id) } - s.metr.RecordBatchTxFailed() + c.metr.RecordBatchTxFailed() } -// TxConfirmed marks a transaction as confirmed on L1. Unfortunately even if all frames in -// a channel have been marked as confirmed on L1 the channel may be invalid & need to be -// resubmitted. -// This function may reset the pending channel if the pending channel has timed out. -func (s *channel) TxConfirmed(id string, inclusionBlock eth.BlockID) (bool, []*types.Block) { - s.metr.RecordBatchTxSubmitted() - s.log.Debug("marked transaction as confirmed", "id", id, "block", inclusionBlock) - if _, ok := s.pendingTransactions[id]; !ok { - s.log.Warn("unknown transaction marked as confirmed", "id", id, "block", inclusionBlock) +// TxConfirmed marks a transaction as confirmed on L1. Returns a bool indicating +// whether the channel timed out on chain. +func (c *channel) TxConfirmed(id string, inclusionBlock eth.BlockID) bool { + c.metr.RecordBatchTxSuccess() + c.log.Debug("marked transaction as confirmed", "id", id, "block", inclusionBlock) + if _, ok := c.pendingTransactions[id]; !ok { + c.log.Warn("unknown transaction marked as confirmed", "id", id, "block", inclusionBlock) // TODO: This can occur if we clear the channel while there are still pending transactions // We need to keep track of stale transactions instead - return false, nil + return false + } + delete(c.pendingTransactions, id) + c.confirmedTransactions[id] = inclusionBlock + c.channelBuilder.FramePublished(inclusionBlock.Number) + + // Update min/max inclusion blocks for timeout check + c.minInclusionBlock = min(c.minInclusionBlock, inclusionBlock.Number) + c.maxInclusionBlock = max(c.maxInclusionBlock, inclusionBlock.Number) + + if c.isFullySubmitted() { + c.metr.RecordChannelFullySubmitted(c.ID()) + c.log.Info("Channel is fully submitted", "id", c.ID(), "min_inclusion_block", c.minInclusionBlock, "max_inclusion_block", c.maxInclusionBlock) } - delete(s.pendingTransactions, id) - s.confirmedTransactions[id] = inclusionBlock - s.confirmedTxUpdated = true - s.channelBuilder.FramePublished(inclusionBlock.Number) // If this channel timed out, put the pending blocks back into the local saved blocks // and then reset this state so it can try to build a new channel. - if s.isTimedOut() { - s.metr.RecordChannelTimedOut(s.ID()) - s.log.Warn("Channel timed out", "id", s.ID(), "min_inclusion_block", s.minInclusionBlock, "max_inclusion_block", s.maxInclusionBlock) - return true, s.channelBuilder.Blocks() - } - // If we are done with this channel, record that. - if s.isFullySubmitted() { - s.metr.RecordChannelFullySubmitted(s.ID()) - s.log.Info("Channel is fully submitted", "id", s.ID(), "min_inclusion_block", s.minInclusionBlock, "max_inclusion_block", s.maxInclusionBlock) - return true, nil + if c.isTimedOut() { + c.metr.RecordChannelTimedOut(c.ID()) + c.log.Warn("Channel timed out", "id", c.ID(), "min_inclusion_block", c.minInclusionBlock, "max_inclusion_block", c.maxInclusionBlock) + return true } - return false, nil + return false } // Timeout returns the channel timeout L1 block number. If there is no timeout set, it returns 0. -func (s *channel) Timeout() uint64 { - return s.channelBuilder.Timeout() +func (c *channel) Timeout() uint64 { + return c.channelBuilder.Timeout() } -// updateInclusionBlocks finds the first & last confirmed tx and saves its inclusion numbers -func (s *channel) updateInclusionBlocks() { - if len(s.confirmedTransactions) == 0 || !s.confirmedTxUpdated { - return - } - // If there are confirmed transactions, find the first + last confirmed block numbers - min := uint64(math.MaxUint64) - max := uint64(0) - for _, inclusionBlock := range s.confirmedTransactions { - if inclusionBlock.Number < min { - min = inclusionBlock.Number - } - if inclusionBlock.Number > max { - max = inclusionBlock.Number - } - } - s.minInclusionBlock = min - s.maxInclusionBlock = max - s.confirmedTxUpdated = false -} - -// pendingChannelIsTimedOut returns true if submitted channel has timed out. +// isTimedOut returns true if submitted channel has timed out. // A channel has timed out if the difference in L1 Inclusion blocks between // the first & last included block is greater than or equal to the channel timeout. -func (s *channel) isTimedOut() bool { - // Update min/max inclusion blocks for timeout check - s.updateInclusionBlocks() +func (c *channel) isTimedOut() bool { // Prior to the granite hard fork activating, the use of the shorter ChannelTimeout here may cause the batcher // to believe the channel timed out when it was valid. It would then resubmit the blocks needlessly. // This wastes batcher funds but doesn't cause any problems for the chain progressing safe head. - return s.maxInclusionBlock-s.minInclusionBlock >= s.cfg.ChannelTimeout + return len(c.confirmedTransactions) > 0 && c.maxInclusionBlock-c.minInclusionBlock >= c.cfg.ChannelTimeout } -// pendingChannelIsFullySubmitted returns true if the channel has been fully submitted. -func (s *channel) isFullySubmitted() bool { - // Update min/max inclusion blocks for timeout check - s.updateInclusionBlocks() - return s.IsFull() && len(s.pendingTransactions)+s.PendingFrames() == 0 +// isFullySubmitted returns true if the channel has been fully submitted (all transactions are confirmed). +func (c *channel) isFullySubmitted() bool { + return c.IsFull() && len(c.pendingTransactions)+c.PendingFrames() == 0 } -func (s *channel) NoneSubmitted() bool { - return len(s.confirmedTransactions) == 0 && len(s.pendingTransactions) == 0 +func (c *channel) NoneSubmitted() bool { + return len(c.confirmedTransactions) == 0 && len(c.pendingTransactions) == 0 } -func (s *channel) ID() derive.ChannelID { - return s.channelBuilder.ID() +func (c *channel) ID() derive.ChannelID { + return c.channelBuilder.ID() } // NextTxData dequeues the next frames from the channel and returns them encoded in a tx data packet. @@ -161,68 +130,68 @@ func (s *channel) ID() derive.ChannelID { // until it either doesn't have more frames or the target number of frames is reached. // // NextTxData should only be called after HasTxData returned true. -func (s *channel) NextTxData() txData { - nf := s.cfg.MaxFramesPerTx() - txdata := txData{frames: make([]frameData, 0, nf), asBlob: s.cfg.UseBlobs} - for i := 0; i < nf && s.channelBuilder.HasFrame(); i++ { - frame := s.channelBuilder.NextFrame() +func (c *channel) NextTxData() txData { + nf := c.cfg.MaxFramesPerTx() + txdata := txData{frames: make([]frameData, 0, nf), asBlob: c.cfg.UseBlobs} + for i := 0; i < nf && c.channelBuilder.HasPendingFrame(); i++ { + frame := c.channelBuilder.NextFrame() txdata.frames = append(txdata.frames, frame) } id := txdata.ID().String() - s.log.Debug("returning next tx data", "id", id, "num_frames", len(txdata.frames), "as_blob", txdata.asBlob) - s.pendingTransactions[id] = txdata + c.log.Debug("returning next tx data", "id", id, "num_frames", len(txdata.frames), "as_blob", txdata.asBlob) + c.pendingTransactions[id] = txdata return txdata } -func (s *channel) HasTxData() bool { - if s.IsFull() || // If the channel is full, we should start to submit it - !s.cfg.UseBlobs { // If using calldata, we only send one frame per tx - return s.channelBuilder.HasFrame() +func (c *channel) HasTxData() bool { + if c.IsFull() || // If the channel is full, we should start to submit it + !c.cfg.UseBlobs { // If using calldata, we only send one frame per tx + return c.channelBuilder.HasPendingFrame() } // Collect enough frames if channel is not full yet - return s.channelBuilder.PendingFrames() >= int(s.cfg.MaxFramesPerTx()) + return c.channelBuilder.PendingFrames() >= int(c.cfg.MaxFramesPerTx()) } -func (s *channel) IsFull() bool { - return s.channelBuilder.IsFull() +func (c *channel) IsFull() bool { + return c.channelBuilder.IsFull() } -func (s *channel) FullErr() error { - return s.channelBuilder.FullErr() +func (c *channel) FullErr() error { + return c.channelBuilder.FullErr() } -func (s *channel) CheckTimeout(l1BlockNum uint64) { - s.channelBuilder.CheckTimeout(l1BlockNum) +func (c *channel) CheckTimeout(l1BlockNum uint64) { + c.channelBuilder.CheckTimeout(l1BlockNum) } -func (s *channel) AddBlock(block *types.Block) (*derive.L1BlockInfo, error) { - return s.channelBuilder.AddBlock(block) +func (c *channel) AddBlock(block *types.Block) (*derive.L1BlockInfo, error) { + return c.channelBuilder.AddBlock(block) } -func (s *channel) InputBytes() int { - return s.channelBuilder.InputBytes() +func (c *channel) InputBytes() int { + return c.channelBuilder.InputBytes() } -func (s *channel) ReadyBytes() int { - return s.channelBuilder.ReadyBytes() +func (c *channel) ReadyBytes() int { + return c.channelBuilder.ReadyBytes() } -func (s *channel) OutputBytes() int { - return s.channelBuilder.OutputBytes() +func (c *channel) OutputBytes() int { + return c.channelBuilder.OutputBytes() } -func (s *channel) TotalFrames() int { - return s.channelBuilder.TotalFrames() +func (c *channel) TotalFrames() int { + return c.channelBuilder.TotalFrames() } -func (s *channel) PendingFrames() int { - return s.channelBuilder.PendingFrames() +func (c *channel) PendingFrames() int { + return c.channelBuilder.PendingFrames() } -func (s *channel) OutputFrames() error { - return s.channelBuilder.OutputFrames() +func (c *channel) OutputFrames() error { + return c.channelBuilder.OutputFrames() } // LatestL1Origin returns the latest L1 block origin from all the L2 blocks that have been added to the channel @@ -245,6 +214,6 @@ func (c *channel) OldestL2() eth.BlockID { return c.channelBuilder.OldestL2() } -func (s *channel) Close() { - s.channelBuilder.Close() +func (c *channel) Close() { + c.channelBuilder.Close() } diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 35afb2cbd2676..56069e5bf1603 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -10,6 +10,7 @@ import ( "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/queue" "github.com/ethereum/go-ethereum/core/types" ) @@ -65,7 +66,7 @@ type ChannelBuilder struct { // current channel co derive.ChannelOut // list of blocks in the channel. Saved in case the channel must be rebuilt - blocks []*types.Block + blocks queue.Queue[*types.Block] // latestL1Origin is the latest L1 origin of all the L2 blocks that have been added to the channel latestL1Origin eth.BlockID // oldestL1Origin is the oldest L1 origin of all the L2 blocks that have been added to the channel @@ -75,35 +76,32 @@ type ChannelBuilder struct { // oldestL2 is the oldest L2 block of all the L2 blocks that have been added to the channel oldestL2 eth.BlockID // frames data queue, to be send as txs - frames []frameData + frames queue.Queue[frameData] + // frameCursor tracks which frames in the queue were submitted + // frames[frameCursor] is the next unsubmitted (pending) frame + // frameCursor = len(frames) is reserved for when + // there are no pending (next unsubmitted) frames + frameCursor int // total frames counter numFrames int // total amount of output data of all frames created yet outputBytes int } -// 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) { - co, err := newChannelOut(cfg, rollupCfg) - if err != nil { - return nil, fmt.Errorf("creating channel out: %w", err) - } - +func NewChannelBuilderWithChannelOut(cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64, channelOut derive.ChannelOut) *ChannelBuilder { cb := &ChannelBuilder{ cfg: cfg, rollupCfg: rollupCfg, - co: co, + co: channelOut, } cb.updateDurationTimeout(latestL1OriginBlockNum) - return cb, nil + return cb } -// newChannelOut creates a new channel out based on the given configuration. -func newChannelOut(cfg ChannelConfig, rollupCfg *rollup.Config) (derive.ChannelOut, error) { +// NewChannelOut creates a new channel out based on the given configuration. +func NewChannelOut(cfg ChannelConfig, rollupCfg *rollup.Config) (derive.ChannelOut, error) { spec := rollup.NewChainSpec(rollupCfg) if cfg.BatchType == derive.SpanBatchType { return derive.NewSpanChannelOut( @@ -186,7 +184,7 @@ func (c *ChannelBuilder) AddBlock(block *types.Block) (*derive.L1BlockInfo, erro return l1info, fmt.Errorf("adding block to channel out: %w", err) } - c.blocks = append(c.blocks, block) + c.blocks.Enqueue(block) c.updateSwTimeout(l1info.Number) if l1info.Number > c.latestL1Origin.Number { @@ -308,11 +306,11 @@ func (c *ChannelBuilder) setFullErr(err error) { } // OutputFrames creates new frames with the channel out. It should be called -// after AddBlock and before iterating over available frames with HasFrame and +// after AddBlock and before iterating over pending frames with HasFrame and // NextFrame. // // If the channel isn't full yet, it will conservatively only -// pull readily available frames from the compression output. +// pull pending frames from the compression output. // If it is full, the channel is closed and all remaining // frames will be created, possibly with a small leftover frame. func (c *ChannelBuilder) OutputFrames() error { @@ -383,7 +381,7 @@ func (c *ChannelBuilder) outputFrame() error { id: frameID{chID: c.co.ID(), frameNumber: fn}, data: buf.Bytes(), } - c.frames = append(c.frames, frame) + c.frames.Enqueue(frame) c.numFrames++ c.outputBytes += len(frame.data) return err // possibly io.EOF (last frame) @@ -398,46 +396,47 @@ func (c *ChannelBuilder) Close() { } // TotalFrames returns the total number of frames that were created in this channel so far. -// It does not decrease when the frames queue is being emptied. func (c *ChannelBuilder) TotalFrames() int { return c.numFrames } -// HasFrame returns whether there's any available frame. If true, it can be -// popped using NextFrame(). +// HasPendingFrame returns whether there's any pending frame. If true, it can be +// dequeued using NextFrame(). // // Call OutputFrames before to create new frames from the channel out // compression pipeline. -func (c *ChannelBuilder) HasFrame() bool { - return len(c.frames) > 0 +func (c *ChannelBuilder) HasPendingFrame() bool { + return c.frameCursor < c.frames.Len() } // PendingFrames returns the number of pending frames in the frames queue. -// It is larger zero iff HasFrame() returns true. +// It is larger than zero iff HasFrame() returns true. func (c *ChannelBuilder) PendingFrames() int { - return len(c.frames) + return c.frames.Len() - c.frameCursor } -// NextFrame dequeues the next available frame. -// HasFrame must be called prior to check if there's a next frame available. +// NextFrame returns the next pending frame and increments the frameCursor +// HasFrame must be called prior to check if there a next pending frame exists. // Panics if called when there's no next frame. func (c *ChannelBuilder) NextFrame() frameData { - if len(c.frames) == 0 { + if len(c.frames) <= c.frameCursor { panic("no next frame") } - - f := c.frames[0] - c.frames = c.frames[1:] + f := c.frames[c.frameCursor] + c.frameCursor++ return f } -// PushFrames adds the frames back to the internal frames queue. Panics if not of -// the same channel. -func (c *ChannelBuilder) PushFrames(frames ...frameData) { - for _, f := range frames { - if f.id.chID != c.ID() { - panic("wrong channel") - } - c.frames = append(c.frames, f) +// RewindFrameCursor moves the frameCursor to point at the supplied frame +// only if it is ahead of it. +// Panics if the frame is not in this channel. +func (c *ChannelBuilder) RewindFrameCursor(frame frameData) { + if c.frames.Len() <= int(frame.id.frameNumber) || + len(c.frames[frame.id.frameNumber].data) != len(frame.data) || + c.frames[frame.id.frameNumber].id.chID != frame.id.chID { + panic("cannot rewind to unknown frame") + } + if c.frameCursor > int(frame.id.frameNumber) { + c.frameCursor = int(frame.id.frameNumber) } } diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 957f9ae597395..a6cb9371f9c08 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -3,6 +3,7 @@ package batcher import ( "bytes" "errors" + "fmt" "math" "math/big" "math/rand" @@ -27,6 +28,18 @@ var defaultTestRollupConfig = &rollup.Config{ L2ChainID: big.NewInt(1234), } +// 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) { + co, err := NewChannelOut(cfg, rollupCfg) + if err != nil { + return nil, fmt.Errorf("creating channel out: %w", err) + } + + return NewChannelBuilderWithChannelOut(cfg, rollupCfg, latestL1OriginBlockNum, co), nil +} + // addMiniBlock adds a minimal valid L2 block to the channel builder using the // ChannelBuilder.AddBlock method. func addMiniBlock(cb *ChannelBuilder) error { @@ -106,7 +119,7 @@ func FuzzDurationTimeoutZeroMaxChannelDuration(f *testing.F) { f.Fuzz(func(t *testing.T, l1BlockNum uint64) { channelConfig := defaultTestChannelConfig() channelConfig.MaxChannelDuration = 0 - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) cb.timeout = 0 cb.updateDurationTimeout(l1BlockNum) @@ -129,7 +142,7 @@ func FuzzChannelBuilder_DurationZero(f *testing.F) { // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.MaxChannelDuration = maxChannelDuration - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Whenever the timeout is set to 0, the channel builder should have a duration timeout @@ -156,7 +169,7 @@ func FuzzDurationTimeoutMaxChannelDuration(f *testing.F) { // Create the channel builder channelConfig := defaultTestChannelConfig() channelConfig.MaxChannelDuration = maxChannelDuration - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Whenever the timeout is greater than the l1BlockNum, @@ -190,7 +203,7 @@ func FuzzChannelCloseTimeout(f *testing.F) { channelConfig := defaultTestChannelConfig() channelConfig.ChannelTimeout = channelTimeout channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -218,7 +231,7 @@ func FuzzChannelZeroCloseTimeout(f *testing.F) { channelConfig := defaultTestChannelConfig() channelConfig.ChannelTimeout = channelTimeout channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -245,7 +258,7 @@ func FuzzSeqWindowClose(f *testing.F) { channelConfig := defaultTestChannelConfig() channelConfig.SeqWindowSize = seqWindowSize channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -273,7 +286,7 @@ func FuzzSeqWindowZeroTimeoutClose(f *testing.F) { channelConfig := defaultTestChannelConfig() channelConfig.SeqWindowSize = seqWindowSize channelConfig.SubSafetyMargin = subSafetyMargin - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Check the timeout @@ -299,6 +312,7 @@ func TestChannelBuilderBatchType(t *testing.T) { {"ChannelBuilder_PendingFrames_TotalFrames", ChannelBuilder_PendingFrames_TotalFrames}, {"ChannelBuilder_InputBytes", ChannelBuilder_InputBytes}, {"ChannelBuilder_OutputBytes", ChannelBuilder_OutputBytes}, + {"ChannelBuilder_OutputWrongFramePanic", ChannelBuilder_OutputWrongFramePanic}, } for _, test := range tests { test := test @@ -320,7 +334,7 @@ func TestChannelBuilder_NextFrame(t *testing.T) { channelConfig := defaultTestChannelConfig() // Create a new channel builder - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Mock the internals of `ChannelBuilder.outputFrame` @@ -340,7 +354,7 @@ func TestChannelBuilder_NextFrame(t *testing.T) { }, data: expectedBytes, } - cb.PushFrames(frameData) + cb.frames = append(cb.frames, frameData) // There should only be 1 frame in the channel builder require.Equal(t, 1, cb.PendingFrames()) @@ -355,13 +369,13 @@ func TestChannelBuilder_NextFrame(t *testing.T) { require.PanicsWithValue(t, "no next frame", func() { cb.NextFrame() }) } -// TestChannelBuilder_OutputWrongFramePanic tests that a panic is thrown when a frame is pushed with an invalid frame id +// 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) { channelConfig := defaultTestChannelConfig() channelConfig.BatchType = batchType // Construct a channel builder - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Mock the internals of `ChannelBuilder.outputFrame` @@ -377,7 +391,7 @@ func ChannelBuilder_OutputWrongFramePanic(t *testing.T, batchType uint) { // The frame push should panic since we constructed a new channel out // so the channel out id won't match - require.PanicsWithValue(t, "wrong channel", func() { + require.PanicsWithValue(t, "cannot rewind to unknown frame", func() { frame := frameData{ id: frameID{ chID: co.ID(), @@ -385,7 +399,7 @@ func ChannelBuilder_OutputWrongFramePanic(t *testing.T, batchType uint) { }, data: buf.Bytes(), } - cb.PushFrames(frame) + cb.RewindFrameCursor(frame) }) } @@ -397,7 +411,7 @@ func TestChannelBuilder_OutputFrames(t *testing.T) { channelConfig.InitNoneCompressor() // Construct the channel builder - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.False(t, cb.IsFull()) require.Equal(t, 0, cb.PendingFrames()) @@ -451,7 +465,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(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.False(t, cb.IsFull()) require.Equal(t, 0, cb.PendingFrames()) @@ -509,7 +523,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(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Add a block that overflows the [ChannelOut] @@ -531,7 +545,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(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Count how many a block that overflows the [ChannelOut] @@ -552,7 +566,7 @@ func ChannelBuilder_MaxRLPBytesPerChannelFjord(t *testing.T, batchType uint) { channelConfig.InitNoneCompressor() channelConfig.BatchType = batchType - cb, err = NewChannelBuilder(channelConfig, rollupConfig, latestL1BlockOrigin) + cb, err = newChannelBuilder(channelConfig, rollupConfig, latestL1BlockOrigin) require.NoError(t, err) // try add double the amount of block, it should not error @@ -575,7 +589,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(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.False(t, cb.IsFull()) require.Equal(t, 0, cb.PendingFrames()) @@ -611,7 +625,7 @@ func TestChannelBuilder_FullShadowCompressor(t *testing.T) { } cfg.InitShadowCompressor(derive.Zlib) - cb, err := NewChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err) rng := rand.New(rand.NewSource(420)) @@ -625,11 +639,11 @@ func TestChannelBuilder_FullShadowCompressor(t *testing.T) { require.NoError(cb.OutputFrames()) - require.True(cb.HasFrame()) + require.True(cb.HasPendingFrame()) f := cb.NextFrame() require.Less(len(f.data), int(cfg.MaxFrameSize)) // would fail without fix, full frame - require.False(cb.HasFrame(), "no leftover frame expected") // would fail without fix + require.False(cb.HasPendingFrame(), "no leftover frame expected") // would fail without fix } func ChannelBuilder_AddBlock(t *testing.T, batchType uint) { @@ -643,7 +657,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(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Add a nonsense block to the channel builder @@ -656,8 +670,8 @@ func ChannelBuilder_AddBlock(t *testing.T, batchType uint) { expectedInputBytes = 47 } require.Equal(t, expectedInputBytes, cb.co.InputBytes()) - require.Equal(t, 1, len(cb.blocks)) - require.Equal(t, 0, len(cb.frames)) + require.Equal(t, 1, cb.blocks.Len()) + require.Equal(t, 0, cb.frames.Len()) require.True(t, cb.IsFull()) // Since the channel output is full, the next call to AddBlock @@ -669,7 +683,7 @@ func TestChannelBuilder_CheckTimeout(t *testing.T) { channelConfig := defaultTestChannelConfig() // Construct the channel builder - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Assert timeout is setup correctly @@ -694,7 +708,7 @@ func TestChannelBuilder_CheckTimeoutZeroMaxChannelDuration(t *testing.T) { channelConfig.MaxChannelDuration = 0 // Construct the channel builder - cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) // Without a max channel duration, timeout should not be set @@ -717,7 +731,7 @@ func TestChannelBuilder_FramePublished(t *testing.T) { cfg.SubSafetyMargin = 100 // Construct the channel builder - cb, err := NewChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, latestL1BlockOrigin+cfg.MaxChannelDuration, cb.timeout) @@ -734,7 +748,7 @@ func TestChannelBuilder_FramePublished(t *testing.T) { } func TestChannelBuilder_LatestL1Origin(t *testing.T) { - cb, err := NewChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, eth.BlockID{}, cb.LatestL1Origin()) @@ -756,7 +770,7 @@ func TestChannelBuilder_LatestL1Origin(t *testing.T) { } func TestChannelBuilder_OldestL1Origin(t *testing.T) { - cb, err := NewChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, eth.BlockID{}, cb.OldestL1Origin()) @@ -778,7 +792,7 @@ func TestChannelBuilder_OldestL1Origin(t *testing.T) { } func TestChannelBuilder_LatestL2(t *testing.T) { - cb, err := NewChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, eth.BlockID{}, cb.LatestL2()) @@ -800,7 +814,7 @@ func TestChannelBuilder_LatestL2(t *testing.T) { } func TestChannelBuilder_OldestL2(t *testing.T) { - cb, err := NewChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) + cb, err := newChannelBuilder(defaultTestChannelConfig(), defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(t, err) require.Equal(t, eth.BlockID{}, cb.OldestL2()) @@ -830,7 +844,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(cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err) // initial builder should be empty @@ -858,7 +872,7 @@ func ChannelBuilder_PendingFrames_TotalFrames(t *testing.T, batchType uint) { // empty queue for pf := nf - 1; pf >= 0; pf-- { - require.True(cb.HasFrame()) + require.True(cb.HasPendingFrame()) _ = cb.NextFrame() require.Equal(cb.PendingFrames(), pf) require.Equal(cb.TotalFrames(), nf) @@ -875,7 +889,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(cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err) require.Zero(cb.InputBytes()) @@ -912,7 +926,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(cfg, defaultTestRollupConfig, latestL1BlockOrigin) require.NoError(err, "NewChannelBuilder") require.Zero(cb.OutputBytes()) @@ -932,7 +946,7 @@ func ChannelBuilder_OutputBytes(t *testing.T, batchType uint) { require.Greater(cb.PendingFrames(), 1) var flen int - for cb.HasFrame() { + for cb.HasPendingFrame() { f := cb.NextFrame() flen += len(f.data) } diff --git a/op-batcher/batcher/channel_config.go b/op-batcher/batcher/channel_config.go index 45dc1d4dcfa4a..e62ea26eee45e 100644 --- a/op-batcher/batcher/channel_config.go +++ b/op-batcher/batcher/channel_config.go @@ -104,7 +104,7 @@ func (cc *ChannelConfig) Check() error { // The [ChannelTimeout] must be larger than the [SubSafetyMargin]. // Otherwise, new blocks would always be considered timed out. if cc.ChannelTimeout < cc.SubSafetyMargin { - return ErrInvalidChannelTimeout + return fmt.Errorf("%w: %d < %d", ErrInvalidChannelTimeout, cc.ChannelTimeout, cc.SubSafetyMargin) } // The max frame size must at least be able to accommodate the constant diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go index d7f3c2cc5ea5f..3621fa78525f6 100644 --- a/op-batcher/batcher/channel_config_test.go +++ b/op-batcher/batcher/channel_config_test.go @@ -51,7 +51,7 @@ func TestChannelConfig_Check(t *testing.T) { } for i := 0; i < derive.FrameV0OverHeadSize; i++ { expectedErr := fmt.Sprintf("max frame size %d is less than the minimum 23", i) - i := i // need to udpate Go version... + i := i // need to update Go version... tests = append(tests, test{ input: func() ChannelConfig { cfg := defaultTestChannelConfig() diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index a275fdc1a0a1d..1da2def78da63 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "math" "sync" "github.com/ethereum-optimism/optimism/op-batcher/metrics" @@ -18,6 +19,8 @@ import ( var ErrReorg = errors.New("block does not extend existing chain") +type ChannelOutFactory func(cfg ChannelConfig, rollupCfg *rollup.Config) (derive.ChannelOut, error) + // channelManager stores a contiguous set of blocks & turns them into channels. // Upon receiving tx confirmation (or a tx failure), it does channel error handling. // @@ -32,10 +35,17 @@ type channelManager struct { cfgProvider ChannelConfigProvider rollupCfg *rollup.Config + outFactory ChannelOutFactory + // All blocks since the last request for new tx data. blocks queue.Queue[*types.Block] - // The latest L1 block from all the L2 blocks in the most recently closed channel - l1OriginLastClosedChannel eth.BlockID + // blockCursor is an index into blocks queue. It points at the next block + // to build a channel with. blockCursor = len(blocks) is reserved for when + // there are no blocks ready to build with. + blockCursor int + // The latest L1 block from all the L2 blocks in the most recently submitted channel. + // Used to track channel duration timeouts. + l1OriginLastSubmittedChannel eth.BlockID // The default ChannelConfig to use for the next channel defaultCfg ChannelConfig // last block hash - for reorg detection @@ -47,9 +57,6 @@ type channelManager struct { channelQueue []*channel // used to lookup channels by tx ID upon tx success / failure txChannels map[string]*channel - - // if set to true, prevents production of any new channel frames - closed bool } func NewChannelManager(log log.Logger, metr metrics.Metricer, cfgProvider ChannelConfigProvider, rollupCfg *rollup.Config) *channelManager { @@ -59,25 +66,34 @@ func NewChannelManager(log log.Logger, metr metrics.Metricer, cfgProvider Channe cfgProvider: cfgProvider, defaultCfg: cfgProvider.ChannelConfig(), rollupCfg: rollupCfg, + outFactory: NewChannelOut, txChannels: make(map[string]*channel), } } +func (s *channelManager) SetChannelOutFactory(outFactory ChannelOutFactory) { + s.outFactory = outFactory +} + // Clear clears the entire state of the channel manager. // It is intended to be used before launching op-batcher and after an L2 reorg. -func (s *channelManager) Clear(l1OriginLastClosedChannel eth.BlockID) { +func (s *channelManager) Clear(l1OriginLastSubmittedChannel eth.BlockID) { s.mu.Lock() defer s.mu.Unlock() s.log.Trace("clearing channel manager state") s.blocks.Clear() - s.l1OriginLastClosedChannel = l1OriginLastClosedChannel + s.blockCursor = 0 + s.l1OriginLastSubmittedChannel = l1OriginLastSubmittedChannel s.tip = common.Hash{} - s.closed = false s.currentChannel = nil s.channelQueue = nil s.txChannels = make(map[string]*channel) } +func (s *channelManager) pendingBlocks() int { + return s.blocks.Len() - s.blockCursor +} + // TxFailed records a transaction as failed. It will attempt to resubmit the data // in the failed transaction. func (s *channelManager) TxFailed(_id txID) { @@ -87,31 +103,21 @@ func (s *channelManager) TxFailed(_id txID) { if channel, ok := s.txChannels[id]; ok { delete(s.txChannels, id) channel.TxFailed(id) - if s.closed && channel.NoneSubmitted() { - s.log.Info("Channel has no submitted transactions, clearing for shutdown", "chID", channel.ID()) - s.removePendingChannel(channel) - } } else { s.log.Warn("transaction from unknown channel marked as failed", "id", id) } } -// TxConfirmed marks a transaction as confirmed on L1. Unfortunately even if all frames in -// a channel have been marked as confirmed on L1 the channel may be invalid & need to be -// resubmitted. -// This function may reset the pending channel if the pending channel has timed out. +// 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) { s.mu.Lock() defer s.mu.Unlock() id := _id.String() if channel, ok := s.txChannels[id]; ok { delete(s.txChannels, id) - done, blocks := channel.TxConfirmed(id, inclusionBlock) - if done { - s.removePendingChannel(channel) - if len(blocks) > 0 { - s.blocks.Prepend(blocks...) - } + if timedOut := channel.TxConfirmed(id, inclusionBlock); timedOut { + s.handleChannelInvalidated(channel) } } else { s.log.Warn("transaction from unknown channel marked as confirmed", "id", id) @@ -120,23 +126,48 @@ func (s *channelManager) TxConfirmed(_id txID, inclusionBlock eth.BlockID) { s.log.Debug("marked transaction as confirmed", "id", id, "block", inclusionBlock) } -// removePendingChannel removes the given completed channel from the manager's state. -func (s *channelManager) removePendingChannel(channel *channel) { - if s.currentChannel == channel { - s.currentChannel = nil +// rewindToBlock updates the blockCursor to point at +// the block with the supplied hash, only if that block exists +// in the block queue and the blockCursor is ahead of it. +// Panics if the block is not in state. +func (s *channelManager) rewindToBlock(block eth.BlockID) { + idx := block.Number - s.blocks[0].Number().Uint64() + if s.blocks[idx].Hash() == block.Hash && idx < uint64(s.blockCursor) { + s.blockCursor = int(idx) + } else { + panic("tried to rewind to nonexistent block") } - index := -1 - for i, c := range s.channelQueue { - if c == channel { - index = i - break +} + +// handleChannelInvalidated rewinds the channelManager's blockCursor +// to point at the first block added to the provided channel, +// and removes the channel from the channelQueue, along with +// any channels which are newer than the provided channel. +func (s *channelManager) handleChannelInvalidated(c *channel) { + if len(c.channelBuilder.blocks) > 0 { + // This is usually true, but there is an edge case + // where a channel timed out before any blocks got added. + // In that case we end up with an empty frame (header only), + // and there are no blocks to requeue. + blockID := eth.ToBlockID(c.channelBuilder.blocks[0]) + for _, block := range c.channelBuilder.blocks { + s.metr.RecordL2BlockInPendingQueue(block) } + s.rewindToBlock(blockID) + } else { + s.log.Debug("channelManager.handleChanneInvalidated: channel had no blocks") } - if index < 0 { - s.log.Warn("channel not found in channel queue", "id", channel.ID()) - return + + // Trim provided channel and any older channels: + for i := range s.channelQueue { + if s.channelQueue[i] == c { + s.channelQueue = s.channelQueue[:i] + break + } } - s.channelQueue = append(s.channelQueue[:index], s.channelQueue[index+1:]...) + + // We want to start writing to a new channel, so reset currentChannel. + s.currentChannel = nil } // nextTxData dequeues frames from the channel and returns them encoded in a transaction. @@ -147,6 +178,12 @@ func (s *channelManager) nextTxData(channel *channel) (txData, error) { return txData{}, io.EOF // TODO: not enough data error instead } tx := channel.NextTxData() + + // update s.l1OriginLastSubmittedChannel so that the next + // channel's duration timeout will trigger properly + if channel.LatestL1Origin().Number > s.l1OriginLastSubmittedChannel.Number { + s.l1OriginLastSubmittedChannel = channel.LatestL1Origin() + } s.txChannels[tx.ID().String()] = channel return tx, nil } @@ -187,7 +224,16 @@ func (s *channelManager) TxData(l1Head eth.BlockID) (txData, error) { s.log.Info("Recomputing optimal ChannelConfig: changing DA type and requeing blocks...", "useBlobsBefore", s.defaultCfg.UseBlobs, "useBlobsAfter", newCfg.UseBlobs) - s.Requeue(newCfg) + + // Invalidate the channel so its blocks + // get requeued: + s.handleChannelInvalidated(channel) + + // Set the defaultCfg so new channels + // pick up the new ChannelConfig + s.defaultCfg = newCfg + + // Try again to get data to send on chain. channel, err = s.getReadyChannel(l1Head) if err != nil { return emptyTxData, err @@ -218,14 +264,9 @@ func (s *channelManager) getReadyChannel(l1Head eth.BlockID) (*channel, error) { return firstWithTxData, nil } - if s.closed { - return nil, io.EOF - } - // No pending tx data, so we have to add new blocks to the channel - // If we have no saved blocks, we will not be able to create valid frames - if s.blocks.Len() == 0 { + if s.pendingBlocks() == 0 { return nil, io.EOF } @@ -265,19 +306,22 @@ func (s *channelManager) ensureChannelWithSpace(l1Head eth.BlockID) error { // This will be reassessed at channel submission-time, // but this is our best guess at the appropriate values for now. cfg := s.defaultCfg - pc, err := newChannel(s.log, s.metr, cfg, s.rollupCfg, s.l1OriginLastClosedChannel.Number) + + channelOut, err := s.outFactory(cfg, s.rollupCfg) if err != nil { - return fmt.Errorf("creating new channel: %w", err) + return fmt.Errorf("creating channel out: %w", err) } + pc := newChannel(s.log, s.metr, cfg, s.rollupCfg, s.l1OriginLastSubmittedChannel.Number, channelOut) + s.currentChannel = pc s.channelQueue = append(s.channelQueue, pc) s.log.Info("Created channel", "id", pc.ID(), "l1Head", l1Head, - "l1OriginLastClosedChannel", s.l1OriginLastClosedChannel, - "blocks_pending", s.blocks.Len(), + "blocks_pending", s.pendingBlocks(), + "l1OriginLastSubmittedChannel", s.l1OriginLastSubmittedChannel, "batch_type", cfg.BatchType, "compression_algo", cfg.CompressorConfig.CompressionAlgo, "target_num_frames", cfg.TargetNumFrames, @@ -308,7 +352,7 @@ func (s *channelManager) processBlocks() error { latestL2ref eth.L2BlockRef ) - for i := 0; ; i++ { + for i := s.blockCursor; ; i++ { block, ok := s.blocks.PeekN(i) if !ok { break @@ -332,7 +376,7 @@ func (s *channelManager) processBlocks() error { } } - _, _ = s.blocks.DequeueN(blocksAdded) + s.blockCursor += blocksAdded s.metr.RecordL2BlocksAdded(latestL2ref, blocksAdded, @@ -341,7 +385,7 @@ func (s *channelManager) processBlocks() error { s.currentChannel.ReadyBytes()) s.log.Debug("Added blocks to channel", "blocks_added", blocksAdded, - "blocks_pending", s.blocks.Len(), + "blocks_pending", s.pendingBlocks(), "channel_full", s.currentChannel.IsFull(), "input_bytes", s.currentChannel.InputBytes(), "ready_bytes", s.currentChannel.ReadyBytes(), @@ -358,15 +402,10 @@ func (s *channelManager) outputFrames() error { return nil } - lastClosedL1Origin := s.currentChannel.LatestL1Origin() - if lastClosedL1Origin.Number > s.l1OriginLastClosedChannel.Number { - s.l1OriginLastClosedChannel = lastClosedL1Origin - } - inBytes, outBytes := s.currentChannel.InputBytes(), s.currentChannel.OutputBytes() s.metr.RecordChannelClosed( s.currentChannel.ID(), - s.blocks.Len(), + s.pendingBlocks(), s.currentChannel.TotalFrames(), inBytes, outBytes, @@ -380,17 +419,16 @@ func (s *channelManager) outputFrames() error { s.log.Info("Channel closed", "id", s.currentChannel.ID(), - "blocks_pending", s.blocks.Len(), + "blocks_pending", s.pendingBlocks(), "num_frames", s.currentChannel.TotalFrames(), "input_bytes", inBytes, "output_bytes", outBytes, "oldest_l1_origin", s.currentChannel.OldestL1Origin(), - "l1_origin", lastClosedL1Origin, + "l1_origin", s.currentChannel.LatestL1Origin(), "oldest_l2", s.currentChannel.OldestL2(), "latest_l2", s.currentChannel.LatestL2(), "full_reason", s.currentChannel.FullErr(), "compr_ratio", comprRatio, - "latest_l1_origin", s.l1OriginLastClosedChannel, ) return nil } @@ -426,82 +464,112 @@ func l2BlockRefFromBlockAndL1Info(block *types.Block, l1info *derive.L1BlockInfo var ErrPendingAfterClose = errors.New("pending channels remain after closing channel-manager") -// Close clears any pending channels that are not in-flight already, to leave a clean derivation state. -// Close then marks the remaining current open channel, if any, as "full" so it can be submitted as well. -// Close does NOT immediately output frames for the current remaining channel: -// as this might error, due to limitations on a single channel. -// Instead, this is part of the pending-channel submission work: after closing, -// the caller SHOULD drain pending channels by generating TxData repeatedly until there is none left (io.EOF). -// A ErrPendingAfterClose error will be returned if there are any remaining pending channels to submit. -func (s *channelManager) Close() error { - s.mu.Lock() - defer s.mu.Unlock() - if s.closed { - return nil +// pruneSafeBlocks dequeues blocks from the internal blocks queue +// if they have now become safe. +func (s *channelManager) pruneSafeBlocks(newSafeHead eth.L2BlockRef) { + oldestBlock, ok := s.blocks.Peek() + if !ok { + // no blocks to prune + return } - s.closed = true - s.log.Info("Channel manager is closing") - - // Any pending state can be proactively cleared if there are no submitted transactions - for _, ch := range s.channelQueue { - if ch.NoneSubmitted() { - s.log.Info("Channel has no past or pending submission - dropping", "id", ch.ID()) - s.removePendingChannel(ch) - } else { - s.log.Info("Channel is in-flight and will need to be submitted after close", "id", ch.ID(), "confirmed", len(ch.confirmedTransactions), "pending", len(ch.pendingTransactions)) - } + if newSafeHead.Number+1 == oldestBlock.NumberU64() { + // no blocks to prune + return } - s.log.Info("Reviewed all pending channels on close", "remaining", len(s.channelQueue)) - if s.currentChannel == nil { - return nil + if newSafeHead.Number+1 < oldestBlock.NumberU64() { + // This could happen if there was an L1 reorg. + // Or if the sequencer restarted. + s.log.Warn("safe head reversed, clearing channel manager state", + "oldestBlock", eth.ToBlockID(oldestBlock), + "newSafeBlock", newSafeHead) + // We should restart work from the new safe head, + // and therefore prune all the blocks. + s.Clear(newSafeHead.L1Origin) + return } - // If the channel is already full, we don't need to close it or output frames. - // This would already have happened in TxData. - if !s.currentChannel.IsFull() { - // Force-close the remaining open channel early (if not already closed): - // it will be marked as "full" due to service termination. - s.currentChannel.Close() + numBlocksToDequeue := newSafeHead.Number + 1 - oldestBlock.NumberU64() - // Final outputFrames call in case there was unflushed data in the compressor. - if err := s.outputFrames(); err != nil { - return fmt.Errorf("outputting frames during close: %w", err) - } + if numBlocksToDequeue > uint64(s.blocks.Len()) { + // This could happen if the batcher restarted. + // The sequencer may have derived the safe chain + // from channels sent by a previous batcher instance. + s.log.Warn("safe head above unsafe head, clearing channel manager state", + "unsafeBlock", eth.ToBlockID(s.blocks[s.blocks.Len()-1]), + "newSafeBlock", newSafeHead) + // We should restart work from the new safe head, + // and therefore prune all the blocks. + s.Clear(newSafeHead.L1Origin) + return } - if s.currentChannel.HasTxData() { - // Make it clear to the caller that there is remaining pending work. - return ErrPendingAfterClose + if s.blocks[numBlocksToDequeue-1].Hash() != newSafeHead.Hash { + s.log.Warn("safe chain reorg, clearing channel manager state", + "existingBlock", eth.ToBlockID(s.blocks[numBlocksToDequeue-1]), + "newSafeBlock", newSafeHead) + // We should restart work from the new safe head, + // and therefore prune all the blocks. + s.Clear(newSafeHead.L1Origin) + return + } + + // This shouldn't return an error because + // We already checked numBlocksToDequeue <= s.blocks.Len() + _, _ = s.blocks.DequeueN(int(numBlocksToDequeue)) + s.blockCursor -= int(numBlocksToDequeue) + + if s.blockCursor < 0 { + panic("negative blockCursor") } - return nil } -// Requeue rebuilds the channel manager state by -// rewinding blocks back from the channel queue, and setting the defaultCfg. -func (s *channelManager) Requeue(newCfg ChannelConfig) { - newChannelQueue := []*channel{} - blocksToRequeue := []*types.Block{} - for _, channel := range s.channelQueue { - if !channel.NoneSubmitted() { - newChannelQueue = append(newChannelQueue, channel) - continue +// pruneChannels dequeues channels from the internal channels queue +// if they were built using blocks which are now safe +func (s *channelManager) pruneChannels(newSafeHead eth.L2BlockRef) { + i := 0 + for _, ch := range s.channelQueue { + if ch.LatestL2().Number > newSafeHead.Number { + break } - blocksToRequeue = append(blocksToRequeue, channel.channelBuilder.Blocks()...) + i++ } + s.channelQueue = s.channelQueue[i:] +} - // We put the blocks back at the front of the queue: - s.blocks.Prepend(blocksToRequeue...) +// PendingDABytes returns the current number of bytes pending to be written to the DA layer (from blocks fetched from L2 +// but not yet in a channel). +func (s *channelManager) PendingDABytes() int64 { + f := s.metr.PendingDABytes() + if f >= math.MaxInt64 { + return math.MaxInt64 + } + if f <= math.MinInt64 { + return math.MinInt64 + } + return int64(f) +} - for _, b := range blocksToRequeue { - s.metr.RecordL2BlockInPendingQueue(b) +// CheckExpectedProgress uses the supplied syncStatus to infer +// whether the node providing the status has made the expected +// safe head progress given fully submitted channels held in +// state. +func (m *channelManager) CheckExpectedProgress(syncStatus eth.SyncStatus) error { + for _, ch := range m.channelQueue { + if ch.isFullySubmitted() && // This implies a number of l1 confirmations has passed, depending on how the txmgr was configured + !ch.isTimedOut() && + syncStatus.CurrentL1.Number > ch.maxInclusionBlock && + syncStatus.SafeL2.Number < ch.LatestL2().Number { + return errors.New("safe head did not make expected progress") + } } + return nil +} - // Channels which where already being submitted are put back - s.channelQueue = newChannelQueue - s.currentChannel = nil - // Setting the defaultCfg will cause new channels - // to pick up the new ChannelConfig - s.defaultCfg = newCfg +func (m *channelManager) LastStoredBlock() eth.BlockID { + if m.blocks.Len() == 0 { + return eth.BlockID{} + } + return eth.ToBlockID(m.blocks[m.blocks.Len()-1]) } diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index fac34f8c931ec..32aae1b06dd1e 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -40,10 +40,6 @@ func TestChannelManagerBatchType(t *testing.T) { {"ChannelManagerReturnsErrReorgWhenDrained", ChannelManagerReturnsErrReorgWhenDrained}, {"ChannelManager_Clear", ChannelManager_Clear}, {"ChannelManager_TxResend", ChannelManager_TxResend}, - {"ChannelManagerCloseBeforeFirstUse", ChannelManagerCloseBeforeFirstUse}, - {"ChannelManagerCloseNoPendingChannel", ChannelManagerCloseNoPendingChannel}, - {"ChannelManagerClosePendingChannel", ChannelManagerClosePendingChannel}, - {"ChannelManagerCloseAllTxsFailed", ChannelManagerCloseAllTxsFailed}, } for _, test := range tests { test := test @@ -130,7 +126,7 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { // Channel Manager state should be empty by default require.Empty(m.blocks) - require.Equal(eth.BlockID{}, m.l1OriginLastClosedChannel) + require.Equal(eth.BlockID{}, m.l1OriginLastSubmittedChannel) require.Equal(common.Hash{}, m.tip) require.Nil(m.currentChannel) require.Empty(m.channelQueue) @@ -154,15 +150,14 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { // Process the blocks // We should have a pending channel with 1 frame - // and no more blocks since processBlocks consumes - // the list + require.NoError(m.processBlocks()) require.NoError(m.currentChannel.channelBuilder.co.Flush()) require.NoError(m.outputFrames()) _, err := m.nextTxData(m.currentChannel) require.NoError(err) - require.NotNil(m.l1OriginLastClosedChannel) - require.Len(m.blocks, 0) + require.Equal(m.blockCursor, len(m.blocks)) + require.NotNil(m.l1OriginLastSubmittedChannel) require.Equal(newL1Tip, m.tip) require.Len(m.currentChannel.pendingTransactions, 1) @@ -173,7 +168,7 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { ParentHash: a.Hash(), }, nil, nil, nil) require.NoError(m.AddL2Block(b)) - require.Len(m.blocks, 1) + require.Equal(m.blockCursor, len(m.blocks)-1) require.Equal(b.Hash(), m.tip) safeL1Origin := eth.BlockID{ @@ -184,7 +179,7 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { // Check that the entire channel manager state cleared require.Empty(m.blocks) - require.Equal(uint64(123), m.l1OriginLastClosedChannel.Number) + require.Equal(uint64(123), m.l1OriginLastSubmittedChannel.Number) require.Equal(common.Hash{}, m.tip) require.Nil(m.currentChannel) require.Empty(m.channelQueue) @@ -228,220 +223,6 @@ func ChannelManager_TxResend(t *testing.T, batchType uint) { require.Len(fs, 1) } -// ChannelManagerCloseBeforeFirstUse ensures that the channel manager -// will not produce any frames if closed immediately. -func ChannelManagerCloseBeforeFirstUse(t *testing.T, batchType uint) { - require := require.New(t) - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - log := testlog.Logger(t, log.LevelCrit) - m := NewChannelManager(log, metrics.NoopMetrics, - channelManagerTestConfig(10000, batchType), - defaultTestRollupConfig, - ) - m.Clear(eth.BlockID{}) - - a := derivetest.RandomL2BlockWithChainId(rng, 4, defaultTestRollupConfig.L2ChainID) - - require.NoError(m.Close(), "Expected to close channel manager gracefully") - - err := m.AddL2Block(a) - require.NoError(err, "Failed to add L2 block") - - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected closed channel manager to contain no tx data") -} - -// ChannelManagerCloseNoPendingChannel ensures that the channel manager -// can gracefully close with no pending channels, and will not emit any new -// channel frames. -func ChannelManagerCloseNoPendingChannel(t *testing.T, batchType uint) { - require := require.New(t) - log := testlog.Logger(t, log.LevelCrit) - cfg := channelManagerTestConfig(10000, batchType) - cfg.CompressorConfig.TargetOutputSize = 1 // full on first block - cfg.ChannelTimeout = 1000 - m := NewChannelManager(log, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - m.Clear(eth.BlockID{}) - a := newMiniL2Block(0) - b := newMiniL2BlockWithNumberParent(0, big.NewInt(1), a.Hash()) - - err := m.AddL2Block(a) - require.NoError(err, "Failed to add L2 block") - - txdata, err := m.TxData(eth.BlockID{}) - require.NoError(err, "Expected channel manager to return valid tx data") - - m.TxConfirmed(txdata.ID(), eth.BlockID{}) - - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected channel manager to EOF") - - require.NoError(m.Close(), "Expected to close channel manager gracefully") - - err = m.AddL2Block(b) - require.NoError(err, "Failed to add L2 block") - - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected closed channel manager to return no new tx data") -} - -// ChannelManagerClosePendingChannel ensures that the channel manager -// can gracefully close with a pending channel, and will not produce any -// new channel frames after this point. -func ChannelManagerClosePendingChannel(t *testing.T, batchType uint) { - require := require.New(t) - // The number of batch txs depends on compression of the random data, hence the static test RNG seed. - // Example of different RNG seed that creates less than 2 frames: 1698700588902821588 - rng := rand.New(rand.NewSource(123)) - log := testlog.Logger(t, log.LevelError) - cfg := channelManagerTestConfig(10_000, batchType) - cfg.ChannelTimeout = 1000 - m := NewChannelManager(log, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - m.Clear(eth.BlockID{}) - - numTx := 20 // Adjust number of txs to make 2 frames - a := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) - - err := m.AddL2Block(a) - require.NoError(err, "Failed to add L2 block") - - txdata, err := m.TxData(eth.BlockID{}) - require.NoError(err, "Expected channel manager to produce valid tx data") - log.Info("generated first tx data", "len", txdata.Len()) - - m.TxConfirmed(txdata.ID(), eth.BlockID{}) - - require.ErrorIs(m.Close(), ErrPendingAfterClose, "Expected channel manager to error on close because of pending tx data") - - txdata, err = m.TxData(eth.BlockID{}) - require.NoError(err, "Expected channel manager to produce tx data from remaining L2 block data") - log.Info("generated more tx data", "len", txdata.Len()) - - m.TxConfirmed(txdata.ID(), eth.BlockID{}) - - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected channel manager to have no more tx data") - - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected closed channel manager to produce no more tx data") -} - -// ChannelManager_Close_PartiallyPendingChannel ensures that the channel manager -// can gracefully close with a pending channel, where a block is still waiting -// inside the compressor to be flushed. -// -// This test runs only for singular batches on purpose. -// The SpanChannelOut writes full span batches to the compressor for -// every new block that's added, so NonCompressor cannot be used to -// set up a scenario where data is only partially flushed. -// Couldn't get the test to work even with modifying NonCompressor -// to flush half-way through writing to the compressor... -func TestChannelManager_Close_PartiallyPendingChannel(t *testing.T) { - require := require.New(t) - // The number of batch txs depends on compression of the random data, hence the static test RNG seed. - // Example of different RNG seed that creates less than 2 frames: 1698700588902821588 - rng := rand.New(rand.NewSource(123)) - log := testlog.Logger(t, log.LevelError) - cfg := ChannelConfig{ - MaxFrameSize: 2200, - ChannelTimeout: 1000, - TargetNumFrames: 100, - } - cfg.InitNoneCompressor() - m := NewChannelManager(log, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - m.Clear(eth.BlockID{}) - - numTx := 3 // Adjust number of txs to make 2 frames - a := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) - b := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) - bHeader := b.Header() - bHeader.Number = new(big.Int).Add(a.Number(), big.NewInt(1)) - bHeader.ParentHash = a.Hash() - b = b.WithSeal(bHeader) - - require.NoError(m.AddL2Block(a), "adding 1st L2 block") - require.NoError(m.AddL2Block(b), "adding 2nd L2 block") - - // Inside TxData, the two blocks queued above are written to the compressor. - // The NonCompressor will flush the first, but not the second block, when - // adding the second block, setting up the test with a partially flushed - // compressor. - txdata, err := m.TxData(eth.BlockID{}) - require.NoError(err, "Expected channel manager to produce valid tx data") - log.Info("generated first tx data", "len", txdata.Len()) - - m.TxConfirmed(txdata.ID(), eth.BlockID{}) - - // ensure no new ready data before closing - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected unclosed channel manager to only return a single frame") - - require.ErrorIs(m.Close(), ErrPendingAfterClose, "Expected channel manager to error on close because of pending tx data") - require.NotNil(m.currentChannel) - require.ErrorIs(m.currentChannel.FullErr(), ErrTerminated, "Expected current channel to be terminated by Close") - - txdata, err = m.TxData(eth.BlockID{}) - require.NoError(err, "Expected channel manager to produce tx data from remaining L2 block data") - log.Info("generated more tx data", "len", txdata.Len()) - - m.TxConfirmed(txdata.ID(), eth.BlockID{}) - - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected closed channel manager to produce no more tx data") -} - -// ChannelManagerCloseAllTxsFailed ensures that the channel manager -// can gracefully close after producing transaction frames if none of these -// have successfully landed on chain. -func ChannelManagerCloseAllTxsFailed(t *testing.T, batchType uint) { - require := require.New(t) - rng := rand.New(rand.NewSource(1357)) - log := testlog.Logger(t, log.LevelCrit) - cfg := channelManagerTestConfig(100, batchType) - cfg.TargetNumFrames = 1000 - cfg.InitNoneCompressor() - m := NewChannelManager(log, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - m.Clear(eth.BlockID{}) - - a := derivetest.RandomL2BlockWithChainId(rng, 1000, defaultTestRollupConfig.L2ChainID) - - err := m.AddL2Block(a) - require.NoError(err, "Failed to add L2 block") - - drainTxData := func() (txdatas []txData) { - for { - txdata, err := m.TxData(eth.BlockID{}) - if err == io.EOF { - return - } - require.NoError(err, "Expected channel manager to produce valid tx data") - txdatas = append(txdatas, txdata) - } - } - - txdatas := drainTxData() - require.NotEmpty(txdatas) - - for _, txdata := range txdatas { - m.TxFailed(txdata.ID()) - } - - // Show that this data will continue to be emitted as long as the transaction - // fails and the channel manager is not closed - txdatas1 := drainTxData() - require.NotEmpty(txdatas) - require.ElementsMatch(txdatas, txdatas1, "expected same txdatas on re-attempt") - - for _, txdata := range txdatas1 { - m.TxFailed(txdata.ID()) - } - - require.NoError(m.Close(), "Expected to close channel manager gracefully") - - _, err = m.TxData(eth.BlockID{}) - require.ErrorIs(err, io.EOF, "Expected closed channel manager to produce no more tx data") -} - func TestChannelManager_ChannelCreation(t *testing.T) { l := testlog.Logger(t, log.LevelCrit) const maxChannelDuration = 15 @@ -475,7 +256,7 @@ func TestChannelManager_ChannelCreation(t *testing.T) { t.Run(test.name, func(t *testing.T) { m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - m.l1OriginLastClosedChannel = test.safeL1Block + m.l1OriginLastSubmittedChannel = test.safeL1Block require.Nil(t, m.currentChannel) require.NoError(t, m.ensureChannelWithSpace(eth.BlockID{})) @@ -543,10 +324,12 @@ func TestChannelManager_TxData(t *testing.T) { // * One when the channelManager was created // * One when the channel is about to be submitted - // * Potentially one more if the replacement channel is about to be submitted, - // this only happens when going from calldata->blobs because - // the channel is no longer ready to send until more data - // is added. + // * Potentially one more when the replacement channel + // is not immediately ready to be submitted, but later + // becomes ready after more data is added. + // This only happens when going from calldata->blobs because + // the channel is not immediately ready to send until more data + // is added due to blob channels having greater capacity. numExpectedAssessments int } @@ -591,7 +374,7 @@ func TestChannelManager_TxData(t *testing.T) { // we get some data to submit var data txData for { - m.blocks = []*types.Block{blockA} + m.blocks = append(m.blocks, blockA) data, err = m.TxData(eth.BlockID{}) if err == nil && data.Len() > 0 { break @@ -609,16 +392,15 @@ func TestChannelManager_TxData(t *testing.T) { } -// TestChannelManager_Requeue seeds the channel manager with blocks, +// TestChannelManager_handleChannelInvalidated seeds the channel manager with blocks, // takes a state snapshot, triggers the blocks->channels pipeline, -// and then calls Requeue. Finally, it asserts the channel manager's -// state is equal to the snapshot. It repeats this for a channel -// which has a pending transaction and verifies that Requeue is then -// a noop. -func TestChannelManager_Requeue(t *testing.T) { +// and then calls handleChannelInvalidated. It asserts on the final state of +// the channel manager. +func TestChannelManager_handleChannelInvalidated(t *testing.T) { l := testlog.Logger(t, log.LevelCrit) cfg := channelManagerTestConfig(100, derive.SingularBatchType) - m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) + metrics := new(metrics.TestMetrics) + m := NewChannelManager(l, metrics, cfg, defaultTestRollupConfig) // Seed channel manager with blocks rng := rand.New(rand.NewSource(99)) @@ -631,40 +413,271 @@ func TestChannelManager_Requeue(t *testing.T) { m.blocks = stateSnapshot require.Empty(t, m.channelQueue) + // Place an old channel in the queue. + // This channel should not be affected by + // a requeue or a later channel timing out. + oldChannel := newChannel(l, nil, m.defaultCfg, defaultTestRollupConfig, 0, nil) + oldChannel.Close() + m.channelQueue = []*channel{oldChannel} + require.Len(t, m.channelQueue, 1) + + // Setup initial metrics + metrics.RecordL2BlockInPendingQueue(blockA) + metrics.RecordL2BlockInPendingQueue(blockB) + pendingBytesBefore := metrics.PendingBlocksBytesCurrent + // Trigger the blocks -> channelQueue data pipelining require.NoError(t, m.ensureChannelWithSpace(eth.BlockID{})) - require.NotEmpty(t, m.channelQueue) + require.Len(t, m.channelQueue, 2) require.NoError(t, m.processBlocks()) // Assert that at least one block was processed into the channel - require.NotContains(t, m.blocks, blockA) + require.Equal(t, 1, m.blockCursor) + + // Check metric decreased + metricsDelta := metrics.PendingBlocksBytesCurrent - pendingBytesBefore + require.Negative(t, metricsDelta) - // Call the function we are testing - m.Requeue(m.defaultCfg) + l1OriginBefore := m.l1OriginLastSubmittedChannel + + m.handleChannelInvalidated(m.currentChannel) // Ensure we got back to the state above require.Equal(t, m.blocks, stateSnapshot) - require.Empty(t, m.channelQueue) + require.Contains(t, m.channelQueue, oldChannel) + require.Len(t, m.channelQueue, 1) + + // Check metric came back up to previous value + require.Equal(t, pendingBytesBefore, metrics.PendingBlocksBytesCurrent) + + // Ensure the l1OridingLastSubmittedChannel was + // not changed. This ensures the next channel + // has its duration timeout deadline computed + // properly. + require.Equal(t, l1OriginBefore, m.l1OriginLastSubmittedChannel) // Trigger the blocks -> channelQueue data pipelining again require.NoError(t, m.ensureChannelWithSpace(eth.BlockID{})) require.NotEmpty(t, m.channelQueue) require.NoError(t, m.processBlocks()) +} - // Assert that at least one block was processed into the channel - require.NotContains(t, m.blocks, blockA) +func TestChannelManager_PruneBlocks(t *testing.T) { + l := testlog.Logger(t, log.LevelDebug) + cfg := channelManagerTestConfig(100, derive.SingularBatchType) + m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - // Now mark the 0th channel in the queue as already - // starting to send on chain - channel0 := m.channelQueue[0] - channel0.pendingTransactions["foo"] = txData{} - require.False(t, channel0.NoneSubmitted()) + a := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, nil, nil, nil) + b := types.NewBlock(&types.Header{ // This will shortly become the safe head + Number: big.NewInt(1), + ParentHash: a.Hash(), + }, nil, nil, nil) + c := types.NewBlock(&types.Header{ + Number: big.NewInt(2), + ParentHash: b.Hash(), + }, nil, nil, nil) - // Call the function we are testing - m.Requeue(m.defaultCfg) + require.NoError(t, m.AddL2Block(a)) + m.blockCursor += 1 + require.NoError(t, m.AddL2Block(b)) + m.blockCursor += 1 + require.NoError(t, m.AddL2Block(c)) + m.blockCursor += 1 + + // Normal path + m.pruneSafeBlocks(eth.L2BlockRef{ + Hash: b.Hash(), + Number: b.NumberU64(), + }) + require.Equal(t, queue.Queue[*types.Block]{c}, m.blocks) + + // Safe chain didn't move, nothing to prune + m.pruneSafeBlocks(eth.L2BlockRef{ + Hash: b.Hash(), + Number: b.NumberU64(), + }) + require.Equal(t, queue.Queue[*types.Block]{c}, m.blocks) + + // Safe chain moved beyond the blocks we had + // state should be cleared + m.pruneSafeBlocks(eth.L2BlockRef{ + Hash: c.Hash(), + Number: uint64(99), + }) + require.Equal(t, queue.Queue[*types.Block]{}, m.blocks) + + // No blocks to prune, NOOP + m.pruneSafeBlocks(eth.L2BlockRef{ + Hash: c.Hash(), + Number: c.NumberU64(), + }) + require.Equal(t, queue.Queue[*types.Block]{}, m.blocks) + + // Put another block in + d := types.NewBlock(&types.Header{ + Number: big.NewInt(3), + ParentHash: c.Hash(), + }, nil, nil, nil) + require.NoError(t, m.AddL2Block(d)) + m.blockCursor += 1 - // The requeue shouldn't affect the pending channel - require.Contains(t, m.channelQueue, channel0) + // Safe chain reorg + // state should be cleared + m.pruneSafeBlocks(eth.L2BlockRef{ + Hash: a.Hash(), + Number: uint64(3), + }) + require.Equal(t, queue.Queue[*types.Block]{}, m.blocks) + + // Put another block in + require.NoError(t, m.AddL2Block(d)) + m.blockCursor += 1 + + // Safe chain reversed + // state should be cleared + m.pruneSafeBlocks(eth.L2BlockRef{ + Hash: a.Hash(), // unused + Number: uint64(1), + }) + require.Equal(t, queue.Queue[*types.Block]{}, m.blocks) + +} + +func TestChannelManager_PruneChannels(t *testing.T) { + l := testlog.Logger(t, log.LevelCrit) + cfg := channelManagerTestConfig(100, derive.SingularBatchType) + cfg.InitNoneCompressor() + m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - require.NotContains(t, m.blocks, blockA) + A, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, m.rollupCfg, 0) + require.NoError(t, err) + B, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, m.rollupCfg, 0) + require.NoError(t, err) + C, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, m.rollupCfg, 0) + require.NoError(t, err) + + m.channelQueue = []*channel{A, B, C} + + numTx := 1 + rng := rand.New(rand.NewSource(123)) + a0 := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) + a0 = a0.WithSeal(&types.Header{Number: big.NewInt(0)}) + a1 := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) + a1 = a1.WithSeal(&types.Header{Number: big.NewInt(1)}) + b2 := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) + b2 = b2.WithSeal(&types.Header{Number: big.NewInt(2)}) + b3 := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) + b3 = b3.WithSeal(&types.Header{Number: big.NewInt(3)}) + c4 := derivetest.RandomL2BlockWithChainId(rng, numTx, defaultTestRollupConfig.L2ChainID) + c4 = c4.WithSeal(&types.Header{Number: big.NewInt(4)}) + + _, err = A.AddBlock(a0) + require.NoError(t, err) + _, err = A.AddBlock(a1) + require.NoError(t, err) + + _, err = B.AddBlock(b2) + require.NoError(t, err) + _, err = B.AddBlock(b3) + require.NoError(t, err) + + _, err = C.AddBlock(c4) + require.NoError(t, err) + + m.pruneChannels(eth.L2BlockRef{ + Number: uint64(3), + }) + + require.Equal(t, []*channel{C}, m.channelQueue) + + m.pruneChannels(eth.L2BlockRef{ + Number: uint64(4), + }) + + require.Equal(t, []*channel{}, m.channelQueue) + + m.pruneChannels(eth.L2BlockRef{ + Number: uint64(4), + }) + + require.Equal(t, []*channel{}, m.channelQueue) + +} +func TestChannelManager_ChannelOutFactory(t *testing.T) { + type ChannelOutWrapper struct { + derive.ChannelOut + } + + l := testlog.Logger(t, log.LevelCrit) + cfg := channelManagerTestConfig(100, derive.SingularBatchType) + m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) + m.SetChannelOutFactory(func(cfg ChannelConfig, rollupCfg *rollup.Config) (derive.ChannelOut, error) { + co, err := NewChannelOut(cfg, rollupCfg) + if err != nil { + return nil, err + } + // return a wrapper type, to validate that the factory was correctly used by checking the type below + return &ChannelOutWrapper{ + ChannelOut: co, + }, nil + }) + require.NoError(t, m.ensureChannelWithSpace(eth.BlockID{})) + + require.IsType(t, &ChannelOutWrapper{}, m.currentChannel.channelBuilder.co) +} + +func TestChannelManager_CheckExpectedProgress(t *testing.T) { + l := testlog.Logger(t, log.LevelCrit) + cfg := channelManagerTestConfig(100, derive.SingularBatchType) + cfg.InitNoneCompressor() + m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) + + channelMaxInclusionBlockNumber := uint64(3) + channelLatestSafeBlockNumber := uint64(11) + + // Prepare a (dummy) fully submitted channel + // with + // maxInclusionBlock and latest safe block number as above + A, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, m.rollupCfg, 0) + require.NoError(t, err) + rng := rand.New(rand.NewSource(123)) + a0 := derivetest.RandomL2BlockWithChainId(rng, 1, defaultTestRollupConfig.L2ChainID) + a0 = a0.WithSeal(&types.Header{Number: big.NewInt(int64(channelLatestSafeBlockNumber))}) + _, err = A.AddBlock(a0) + require.NoError(t, err) + A.maxInclusionBlock = channelMaxInclusionBlockNumber + A.Close() + A.channelBuilder.frames = nil + A.channelBuilder.frameCursor = 0 + require.True(t, A.isFullySubmitted()) + + m.channelQueue = append(m.channelQueue, A) + + // The current L1 number implies that + // channel A above should have been derived + // from, so we expect safe head to progress to + // the channelLatestSafeBlockNumber. + // Since the safe head moved to 11, there is no error: + ss := eth.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: channelMaxInclusionBlockNumber + 1}, + SafeL2: eth.L2BlockRef{Number: channelLatestSafeBlockNumber}, + } + err = m.CheckExpectedProgress(ss) + require.NoError(t, err) + + // If the currentL1 is as above but the + // safe head is less than channelLatestSafeBlockNumber, + // the method should return an error: + ss.SafeL2 = eth.L2BlockRef{Number: channelLatestSafeBlockNumber - 1} + err = m.CheckExpectedProgress(ss) + require.Error(t, err) + + // If the safe head is still less than channelLatestSafeBlockNumber + // but the currentL1 is _equal_ to the channelMaxInclusionBlockNumber + // there should be no error as that block is still being derived from: + ss.CurrentL1 = eth.L1BlockRef{Number: channelMaxInclusionBlockNumber} + err = m.CheckExpectedProgress(ss) + require.NoError(t, err) } diff --git a/op-batcher/batcher/channel_test.go b/op-batcher/batcher/channel_test.go index 3585ea8b99f60..b36ce9311bcea 100644 --- a/op-batcher/batcher/channel_test.go +++ b/op-batcher/batcher/channel_test.go @@ -1,6 +1,7 @@ package batcher import ( + "fmt" "io" "testing" @@ -23,6 +24,14 @@ func zeroFrameTxID(fn uint16) txID { return txID{frameID{frameNumber: fn}} } +func newChannelWithChannelOut(log log.Logger, metr metrics.Metricer, cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64) (*channel, error) { + channelOut, err := NewChannelOut(cfg, rollupCfg) + if err != nil { + return nil, fmt.Errorf("creating channel out: %w", err) + } + return newChannel(log, metr, cfg, rollupCfg, latestL1OriginBlockNum, channelOut), nil +} + // TestChannelTimeout tests that the channel manager // correctly identifies when a pending channel is timed out. func TestChannelTimeout(t *testing.T) { @@ -44,16 +53,19 @@ func TestChannelTimeout(t *testing.T) { channel := m.currentChannel require.NotNil(t, channel) + // add some pending txs, to be confirmed below + channel.pendingTransactions[zeroFrameTxID(0).String()] = txData{} + channel.pendingTransactions[zeroFrameTxID(1).String()] = txData{} + channel.pendingTransactions[zeroFrameTxID(2).String()] = txData{} + // There are no confirmed transactions so // the pending channel cannot be timed out timeout := channel.isTimedOut() require.False(t, timeout) - // Manually set a confirmed transactions - // To avoid other methods clearing state - channel.confirmedTransactions[zeroFrameTxID(0).String()] = eth.BlockID{Number: 0} - channel.confirmedTransactions[zeroFrameTxID(1).String()] = eth.BlockID{Number: 99} - channel.confirmedTxUpdated = true + // Manually confirm transactions + channel.TxConfirmed(zeroFrameTxID(0).String(), eth.BlockID{Number: 0}) + channel.TxConfirmed(zeroFrameTxID(1).String(), eth.BlockID{Number: 99}) // Since the ChannelTimeout is 100, the // pending channel should not be timed out @@ -62,10 +74,7 @@ func TestChannelTimeout(t *testing.T) { // Add a confirmed transaction with a higher number // than the ChannelTimeout - channel.confirmedTransactions[zeroFrameTxID(2).String()] = eth.BlockID{ - Number: 101, - } - channel.confirmedTxUpdated = true + channel.TxConfirmed(zeroFrameTxID(2).String(), eth.BlockID{Number: 101}) // Now the pending channel should be timed out timeout = channel.isTimedOut() @@ -104,7 +113,7 @@ func TestChannelManager_NextTxData(t *testing.T) { frameNumber: uint16(0), }, } - channel.channelBuilder.PushFrames(frame) + channel.channelBuilder.frames = append(channel.channelBuilder.frames, frame) require.Equal(t, 1, channel.PendingFrames()) // Now the nextTxData function should return the frame @@ -121,7 +130,7 @@ func TestChannel_NextTxData_singleFrameTx(t *testing.T) { require := require.New(t) const n = 6 lgr := testlog.Logger(t, log.LevelWarn) - ch, err := newChannel(lgr, metrics.NoopMetrics, ChannelConfig{ + ch, err := newChannelWithChannelOut(lgr, metrics.NoopMetrics, ChannelConfig{ UseBlobs: false, TargetNumFrames: n, CompressorConfig: compressor.Config{ @@ -133,7 +142,7 @@ func TestChannel_NextTxData_singleFrameTx(t *testing.T) { mockframes := makeMockFrameDatas(chID, n+1) // put multiple frames into channel, but less than target - ch.channelBuilder.PushFrames(mockframes[:n-1]...) + ch.channelBuilder.frames = mockframes[:n-1] requireTxData := func(i int) { require.True(ch.HasTxData(), "expected tx data %d", i) @@ -151,7 +160,7 @@ func TestChannel_NextTxData_singleFrameTx(t *testing.T) { require.False(ch.HasTxData()) // put in last two - ch.channelBuilder.PushFrames(mockframes[n-1 : n+1]...) + ch.channelBuilder.frames = append(ch.channelBuilder.frames, mockframes[n-1:n+1]...) for i := n - 1; i < n+1; i++ { requireTxData(i) } @@ -162,7 +171,7 @@ func TestChannel_NextTxData_multiFrameTx(t *testing.T) { require := require.New(t) const n = eth.MaxBlobsPerBlobTx lgr := testlog.Logger(t, log.LevelWarn) - ch, err := newChannel(lgr, metrics.NoopMetrics, ChannelConfig{ + ch, err := newChannelWithChannelOut(lgr, metrics.NoopMetrics, ChannelConfig{ UseBlobs: true, TargetNumFrames: n, CompressorConfig: compressor.Config{ @@ -174,11 +183,11 @@ func TestChannel_NextTxData_multiFrameTx(t *testing.T) { mockframes := makeMockFrameDatas(chID, n+1) // put multiple frames into channel, but less than target - ch.channelBuilder.PushFrames(mockframes[:n-1]...) + ch.channelBuilder.frames = append(ch.channelBuilder.frames, mockframes[:n-1]...) require.False(ch.HasTxData()) // put in last two - ch.channelBuilder.PushFrames(mockframes[n-1 : n+1]...) + ch.channelBuilder.frames = append(ch.channelBuilder.frames, mockframes[n-1:n+1]...) require.True(ch.HasTxData()) txdata := ch.NextTxData() require.Len(txdata.frames, n) @@ -231,7 +240,8 @@ func TestChannelTxConfirmed(t *testing.T) { frameNumber: uint16(0), }, } - m.currentChannel.channelBuilder.PushFrames(frame) + m.currentChannel.channelBuilder.frames = append(m.currentChannel.channelBuilder.frames, frame) + require.Equal(t, 1, m.currentChannel.PendingFrames()) returnedTxData, err := m.nextTxData(m.currentChannel) expectedTxData := singleFrameTxData(frame) @@ -282,7 +292,7 @@ func TestChannelTxFailed(t *testing.T) { frameNumber: uint16(0), }, } - m.currentChannel.channelBuilder.PushFrames(frame) + m.currentChannel.channelBuilder.frames = append(m.currentChannel.channelBuilder.frames, frame) require.Equal(t, 1, m.currentChannel.PendingFrames()) returnedTxData, err := m.nextTxData(m.currentChannel) expectedTxData := singleFrameTxData(frame) diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index ac8bad7791a78..82be687af325a 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -96,6 +96,19 @@ type CLIConfig struct { // ActiveSequencerCheckDuration is the duration between checks to determine the active sequencer endpoint. ActiveSequencerCheckDuration time.Duration + // ThrottleInterval is the interval between notifying the block builder of the latest DA throttling state, or 0 to + // disable notifications entirely (only recommended for testing). + ThrottleInterval time.Duration + // ThrottleThreshold is the number of pending bytes beyond which the batcher will start throttling future bytes + // written to DA. + ThrottleThreshold uint64 + // ThrottleTxSize is the DA size of a transaction to start throttling when we are over the throttling threshold. + ThrottleTxSize uint64 + // ThrottleBlockSize is the total per-block DA limit to start imposing on block building when we are over the throttling threshold. + ThrottleBlockSize uint64 + // ThrottleAlwaysBlockSize is the total per-block DA limit to always imposing on block building. + ThrottleAlwaysBlockSize uint64 + // TestUseMaxTxSizeForBlobs allows to set the blob size with MaxL1TxSize. // Should only be used for testing purposes. TestUseMaxTxSizeForBlobs bool @@ -195,5 +208,10 @@ func NewConfig(ctx *cli.Context) *CLIConfig { PprofConfig: oppprof.ReadCLIConfig(ctx), RPC: oprpc.ReadCLIConfig(ctx), AltDA: altda.ReadCLIConfig(ctx), + ThrottleThreshold: ctx.Uint64(flags.ThrottleThresholdFlag.Name), + ThrottleInterval: ctx.Duration(flags.ThrottleIntervalFlag.Name), + ThrottleTxSize: ctx.Uint64(flags.ThrottleTxSizeFlag.Name), + ThrottleBlockSize: ctx.Uint64(flags.ThrottleBlockSizeFlag.Name), + ThrottleAlwaysBlockSize: ctx.Uint64(flags.ThrottleAlwaysBlockSizeFlag.Name), } } diff --git a/op-batcher/batcher/config_test.go b/op-batcher/batcher/config_test.go index 4b90ebaccb688..9ce1ab4628fa3 100644 --- a/op-batcher/batcher/config_test.go +++ b/op-batcher/batcher/config_test.go @@ -38,8 +38,11 @@ func validBatcherConfig() batcher.CLIConfig { MetricsConfig: metrics.DefaultCLIConfig(), PprofConfig: oppprof.DefaultCLIConfig(), // The compressor config is not checked in config.Check() - RPC: rpc.DefaultCLIConfig(), - CompressionAlgo: derive.Zlib, + RPC: rpc.DefaultCLIConfig(), + CompressionAlgo: derive.Zlib, + ThrottleThreshold: 0, // no DA throttling + ThrottleInterval: 12 * time.Second, + ThrottleTxSize: 0, } } diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 968e6de3e71ae..ed91c20a4cf30 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -8,8 +8,19 @@ import ( "math/big" _ "net/http/pprof" "sync" + "sync/atomic" "time" + "golang.org/x/sync/errgroup" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup" @@ -17,23 +28,19 @@ import ( "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txmgr" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "golang.org/x/sync/errgroup" ) var ( - ErrBatcherNotRunning = errors.New("batcher is not running") - emptyTxData = txData{ + ErrBatcherNotRunning = errors.New("batcher is not running") + ErrInboxTransactionFailed = errors.New("inbox transaction failed") + emptyTxData = txData{ frames: []frameData{ { data: []byte{}, }, }, } + SetMaxDASizeMethod = "miner_setMaxDASize" ) type txRef struct { @@ -64,6 +71,7 @@ func (r txRef) string(txIDStringer func(txID) string) string { type L1Client interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) + CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) } type L2Client interface { @@ -76,15 +84,16 @@ type RollupClient interface { // DriverSetup is the collection of input/output interfaces and configuration that the driver operates on. type DriverSetup struct { - Log log.Logger - Metr metrics.Metricer - RollupConfig *rollup.Config - Config BatcherConfig - Txmgr txmgr.TxManager - L1Client L1Client - EndpointProvider dial.L2EndpointProvider - ChannelConfig ChannelConfigProvider - AltDA *altda.DAClient + Log log.Logger + Metr metrics.Metricer + RollupConfig *rollup.Config + Config BatcherConfig + Txmgr txmgr.TxManager + L1Client L1Client + EndpointProvider dial.L2EndpointProvider + ChannelConfig ChannelConfigProvider + AltDA *altda.DAClient + ChannelOutFactory ChannelOutFactory } // BatchSubmitter encapsulates a service responsible for submitting L2 tx @@ -99,25 +108,28 @@ type BatchSubmitter struct { killCtx context.Context cancelKillCtx context.CancelFunc + l2BlockAdded chan struct{} // notifies the throttling loop whenever an l2 block is added + mutex sync.Mutex running bool txpoolMutex sync.Mutex // guards txpoolState and txpoolBlockedBlob - txpoolState int + txpoolState TxPoolState txpoolBlockedBlob bool - // lastStoredBlock is the last block loaded into `state`. If it is empty it should be set to the l2 safe head. - lastStoredBlock eth.BlockID - lastL1Tip eth.L1BlockRef - - state *channelManager + state *channelManager + inboxIsEOA atomic.Pointer[bool] } // NewBatchSubmitter initializes the BatchSubmitter driver from a preconfigured DriverSetup func NewBatchSubmitter(setup DriverSetup) *BatchSubmitter { + state := NewChannelManager(setup.Log, setup.Metr, setup.ChannelConfig, setup.RollupConfig) + if setup.ChannelOutFactory != nil { + state.SetChannelOutFactory(setup.ChannelOutFactory) + } return &BatchSubmitter{ DriverSetup: setup, - state: NewChannelManager(setup.Log, setup.Metr, setup.ChannelConfig, setup.RollupConfig), + state: state, } } @@ -135,7 +147,10 @@ func (l *BatchSubmitter) StartBatchSubmitting() error { l.shutdownCtx, l.cancelShutdownCtx = context.WithCancel(context.Background()) l.killCtx, l.cancelKillCtx = context.WithCancel(context.Background()) l.clearState(l.shutdownCtx) - l.lastStoredBlock = eth.BlockID{} + + if err := l.waitForL2Genesis(); err != nil { + return fmt.Errorf("error waiting for L2 genesis: %w", err) + } if l.Config.WaitNodeSync { err := l.waitNodeSync() @@ -144,13 +159,55 @@ func (l *BatchSubmitter) StartBatchSubmitting() error { } } - l.wg.Add(1) - go l.loop() + receiptsCh := make(chan txmgr.TxReceipt[txRef]) + receiptsLoopCtx, cancelReceiptsLoopCtx := context.WithCancel(context.Background()) + throttlingLoopCtx, cancelThrottlingLoopCtx := context.WithCancel(context.Background()) + + // DA throttling loop should always be started except for testing (indicated by ThrottleInterval == 0) + if l.Config.ThrottleInterval > 0 { + l.wg.Add(1) + go l.throttlingLoop(throttlingLoopCtx) + } else { + l.Log.Warn("Throttling loop is DISABLED due to 0 throttle-interval. This should not be disabled in prod.") + } + l.wg.Add(2) + go l.processReceiptsLoop(receiptsLoopCtx, receiptsCh) // receives from receiptsCh + go l.mainLoop(l.shutdownCtx, receiptsCh, cancelReceiptsLoopCtx, cancelThrottlingLoopCtx) // sends on receiptsCh l.Log.Info("Batch Submitter started") return nil } +// waitForL2Genesis waits for the L2 genesis time to be reached. +func (l *BatchSubmitter) waitForL2Genesis() error { + genesisTime := time.Unix(int64(l.RollupConfig.Genesis.L2Time), 0) + now := time.Now() + if now.After(genesisTime) { + return nil + } + + l.Log.Info("Waiting for L2 genesis", "genesisTime", genesisTime) + + // Create a ticker that fires every 30 seconds + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + genesisTrigger := time.After(time.Until(genesisTime)) + + for { + select { + case <-ticker.C: + remaining := time.Until(genesisTime) + l.Log.Info("Waiting for L2 genesis", "remainingTime", remaining.Round(time.Second)) + case <-genesisTrigger: + l.Log.Info("L2 genesis time reached") + return nil + case <-l.shutdownCtx.Done(): + return errors.New("batcher stopped") + } + } +} + func (l *BatchSubmitter) StopBatchSubmittingIfRunning(ctx context.Context) error { err := l.StopBatchSubmitting(ctx) if errors.Is(err, ErrBatcherNotRunning) { @@ -194,11 +251,12 @@ func (l *BatchSubmitter) StopBatchSubmitting(ctx context.Context) error { // 2. Check if the sync status is valid or if we are all the way up to date // 3. Check if it needs to initialize state OR it is lagging (todo: lagging just means race condition?) // 4. Load all new blocks into the local state. +// 5. Dequeue blocks from local state which are now safe. // // If there is a reorg, it will reset the last stored block but not clear the internal state so // the state can be flushed to L1. -func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error { - start, end, err := l.calculateL2BlockRangeToStore(ctx) +func (l *BatchSubmitter) loadBlocksIntoState(syncStatus eth.SyncStatus, ctx context.Context) error { + start, end, err := l.calculateL2BlockRangeToStore(syncStatus) if err != nil { l.Log.Warn("Error calculating L2 block range", "err", err) return err @@ -212,13 +270,11 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error { block, err := l.loadBlockIntoState(ctx, i) if errors.Is(err, ErrReorg) { l.Log.Warn("Found L2 reorg", "block_number", i) - l.lastStoredBlock = eth.BlockID{} return err } else if err != nil { l.Log.Warn("Failed to load block into state", "err", err) return err } - l.lastStoredBlock = eth.ToBlockID(block) latestBlock = block } @@ -251,46 +307,87 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin return nil, fmt.Errorf("adding L2 block to state: %w", err) } + // notify the throttling loop it may be time to initiate throttling without blocking + select { + case l.l2BlockAdded <- struct{}{}: + default: + } + l.Log.Info("Added L2 block to local state", "block", eth.ToBlockID(block), "tx_count", len(block.Transactions()), "time", block.Time()) return block, nil } -// calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state. -// It also takes care of initializing some local state (i.e. will modify l.lastStoredBlock in certain conditions) -func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) { +func (l *BatchSubmitter) getSyncStatus(ctx context.Context) (*eth.SyncStatus, error) { rollupClient, err := l.EndpointProvider.RollupClient(ctx) if err != nil { - return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("getting rollup client: %w", err) + return nil, fmt.Errorf("getting rollup client: %w", err) } - cCtx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout) - defer cancel() + var ( + syncStatus *eth.SyncStatus + backoff = time.Second + maxBackoff = 30 * time.Second + ) + timer := time.NewTimer(backoff) + defer timer.Stop() - syncStatus, err := rollupClient.SyncStatus(cCtx) - // Ensure that we have the sync status - if err != nil { - return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("failed to get sync status: %w", err) + for { + cCtx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout) + syncStatus, err = rollupClient.SyncStatus(cCtx) + cancel() + + // Ensure that we have the sync status + if err != nil { + return nil, fmt.Errorf("failed to get sync status: %w", err) + } + + // If we have a head, break out of the loop + if syncStatus.HeadL1 != (eth.L1BlockRef{}) { + break + } + + // Empty sync status, implement backoff + l.Log.Info("Received empty sync status, backing off", "backoff", backoff) + select { + case <-timer.C: + backoff *= 2 + backoff = min(backoff, maxBackoff) + // Reset timer to tick of the new backoff time again + timer.Reset(backoff) + case <-ctx.Done(): + return nil, ctx.Err() + } } + + return syncStatus, nil +} + +// calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state. +func (l *BatchSubmitter) calculateL2BlockRangeToStore(syncStatus eth.SyncStatus) (eth.BlockID, eth.BlockID, error) { if syncStatus.HeadL1 == (eth.L1BlockRef{}) { return eth.BlockID{}, eth.BlockID{}, errors.New("empty sync status") } - - // Check last stored to see if it needs to be set on startup OR set if is lagged behind. - // It lagging implies that the op-node processed some batches that were submitted prior to the current instance of the batcher being alive. - if l.lastStoredBlock == (eth.BlockID{}) { - l.Log.Info("Starting batch-submitter work at safe-head", "safe", syncStatus.SafeL2) - l.lastStoredBlock = syncStatus.SafeL2.ID() - } else if l.lastStoredBlock.Number < syncStatus.SafeL2.Number { - l.Log.Warn("Last submitted block lagged behind L2 safe head: batch submission will continue from the safe head now", "last", l.lastStoredBlock, "safe", syncStatus.SafeL2) - l.lastStoredBlock = syncStatus.SafeL2.ID() - } - // Check if we should even attempt to load any blocks. TODO: May not need this check if syncStatus.SafeL2.Number >= syncStatus.UnsafeL2.Number { - return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("L2 safe head(%d) ahead of L2 unsafe head(%d)", syncStatus.SafeL2.Number, syncStatus.UnsafeL2.Number) + return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("L2 safe head(%d) >= L2 unsafe head(%d)", syncStatus.SafeL2.Number, syncStatus.UnsafeL2.Number) } - return l.lastStoredBlock, syncStatus.UnsafeL2.ID(), nil + lastStoredBlock := l.state.LastStoredBlock() + start := lastStoredBlock + end := syncStatus.UnsafeL2.ID() + + // Check last stored block to see if it is empty or has lagged behind. + // It lagging implies that the op-node processed some batches that + // were submitted prior to the current instance of the batcher being alive. + if lastStoredBlock == (eth.BlockID{}) { + l.Log.Info("Resuming batch-submitter work at safe-head", "safe", syncStatus.SafeL2) + start = syncStatus.SafeL2.ID() + } else if lastStoredBlock.Number < syncStatus.SafeL2.Number { + l.Log.Warn("Last stored block lagged behind L2 safe head: batch submission will continue from the safe head now", "last", lastStoredBlock, "safe", syncStatus.SafeL2) + start = syncStatus.SafeL2.ID() + } + + return start, end, nil } // The following things occur: @@ -304,6 +401,8 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth. // Submitted batch, but it is not valid // Missed L2 block somehow. +type TxPoolState int + const ( // Txpool states. Possible state transitions: // TxpoolGood -> TxpoolBlocked: @@ -313,15 +412,30 @@ const ( // send a cancellation transaction. // TxpoolCancelPending -> TxpoolGood: // happens once the cancel transaction completes, whether successfully or in error. - TxpoolGood int = iota + TxpoolGood TxPoolState = iota TxpoolBlocked TxpoolCancelPending ) -func (l *BatchSubmitter) loop() { +// setTxPoolState locks the mutex, sets the parameters to the supplied ones, and release the mutex. +func (l *BatchSubmitter) setTxPoolState(txPoolState TxPoolState, txPoolBlockedBlob bool) { + l.txpoolMutex.Lock() + l.txpoolState = txPoolState + l.txpoolBlockedBlob = txPoolBlockedBlob + l.txpoolMutex.Unlock() +} + +// mainLoop periodically: +// - polls the sequencer, +// - prunes the channel manager state (i.e. safe blocks) +// - loads unsafe blocks from the sequencer +// - drives the creation of channels and frames +// - sends transactions to the DA layer +func (l *BatchSubmitter) mainLoop(ctx context.Context, receiptsCh chan txmgr.TxReceipt[txRef], receiptsLoopCancel, throttlingLoopCancel context.CancelFunc) { defer l.wg.Done() + defer receiptsLoopCancel() + defer throttlingLoopCancel() - receiptsCh := make(chan txmgr.TxReceipt[txRef]) queue := txmgr.NewQueue[txRef](l.killCtx, l.Txmgr, l.Config.MaxPendingTransactions) daGroup := &errgroup.Group{} // errgroup with limit of 0 means no goroutine is able to run concurrently, @@ -330,105 +444,160 @@ func (l *BatchSubmitter) loop() { daGroup.SetLimit(int(l.Config.MaxConcurrentDARequests)) } - // start the receipt/result processing loop - receiptLoopDone := make(chan struct{}) - defer close(receiptLoopDone) // shut down receipt loop - l.txpoolMutex.Lock() l.txpoolState = TxpoolGood l.txpoolMutex.Unlock() - go func() { - for { - select { - case r := <-receiptsCh: - l.txpoolMutex.Lock() - if errors.Is(r.Err, txpool.ErrAlreadyReserved) && l.txpoolState == TxpoolGood { - l.txpoolState = TxpoolBlocked - l.txpoolBlockedBlob = r.ID.isBlob - l.Log.Info("incompatible tx in txpool", "is_blob", r.ID.isBlob) - } else if r.ID.isCancel && l.txpoolState == TxpoolCancelPending { - // Set state to TxpoolGood even if the cancellation transaction ended in error - // since the stuck transaction could have cleared while we were waiting. - l.txpoolState = TxpoolGood - l.Log.Info("txpool may no longer be blocked", "err", r.Err) - } - l.txpoolMutex.Unlock() - l.Log.Info("Handling receipt", "id", r.ID) - l.handleReceipt(r) - case <-receiptLoopDone: - l.Log.Info("Receipt processing loop done") - return - } - } - }() + + l.l2BlockAdded = make(chan struct{}) + defer close(l.l2BlockAdded) ticker := time.NewTicker(l.Config.PollInterval) defer ticker.Stop() - publishAndWait := func() { - l.publishStateToL1(queue, receiptsCh, daGroup) - if !l.Txmgr.IsClosed() { - if l.Config.UseAltDA { - l.Log.Info("Waiting for altDA writes to complete...") - err := daGroup.Wait() - if err != nil { - l.Log.Error("Error returned by one of the altda goroutines waited on", "err", err) - } - } - l.Log.Info("Waiting for L1 txs to be confirmed...") - err := queue.Wait() - if err != nil { - l.Log.Error("Error returned by one of the txmgr goroutines waited on", "err", err) - } - } else { - l.Log.Info("Txmgr is closed, remaining channel data won't be sent") - } - } - for { select { case <-ticker.C: + if !l.checkTxpool(queue, receiptsCh) { continue } - if err := l.loadBlocksIntoState(l.shutdownCtx); errors.Is(err, ErrReorg) { - err := l.state.Close() - if err != nil { - if errors.Is(err, ErrPendingAfterClose) { - l.Log.Warn("Closed channel manager to handle L2 reorg with pending channel(s) remaining - submitting") - } else { - l.Log.Error("Error closing the channel manager to handle a L2 reorg", "err", err) - } - } - // on reorg we want to publish all pending state then wait until each result clears before resetting - // the state. - publishAndWait() - l.clearState(l.shutdownCtx) + + syncStatus, err := l.getSyncStatus(l.shutdownCtx) + if err != nil { + l.Log.Warn("could not get sync status", "err", err) continue } - l.publishStateToL1(queue, receiptsCh, daGroup) - case <-l.shutdownCtx.Done(): - if l.Txmgr.IsClosed() { - l.Log.Info("Txmgr is closed, remaining channel data won't be sent") - return - } - // This removes any never-submitted pending channels, so these do not have to be drained with transactions. - // Any remaining unfinished channel is terminated, so its data gets submitted. - err := l.state.Close() + + l.state.pruneSafeBlocks(syncStatus.SafeL2) + l.state.pruneChannels(syncStatus.SafeL2) + + err = l.state.CheckExpectedProgress(*syncStatus) if err != nil { - if errors.Is(err, ErrPendingAfterClose) { - l.Log.Warn("Closed channel manager on shutdown with pending channel(s) remaining - submitting") - } else { - l.Log.Error("Error closing the channel manager on shutdown", "err", err) - } + l.Log.Warn("error checking expected progress, clearing state and waiting for node sync", "err", err) + l.waitNodeSyncAndClearState() + continue + } + + if err := l.loadBlocksIntoState(*syncStatus, l.shutdownCtx); errors.Is(err, ErrReorg) { + l.Log.Warn("error loading blocks, clearing state and waiting for node sync", "err", err) + l.waitNodeSyncAndClearState() + continue + } + + l.publishStateToL1(queue, receiptsCh, daGroup, l.Config.PollInterval) + case <-ctx.Done(): + if err := queue.Wait(); err != nil { + l.Log.Error("error waiting for transactions to complete", "err", err) } - publishAndWait() - l.Log.Info("Finished publishing all remaining channel data") + l.Log.Warn("main loop returning") + return + } + } +} + +// processReceiptsLoop handles transaction receipts from the DA layer +func (l *BatchSubmitter) processReceiptsLoop(ctx context.Context, receiptsCh chan txmgr.TxReceipt[txRef]) { + defer l.wg.Done() + l.Log.Info("Starting receipts processing loop") + for { + select { + case r := <-receiptsCh: + if errors.Is(r.Err, txpool.ErrAlreadyReserved) && l.txpoolState == TxpoolGood { + l.setTxPoolState(TxpoolBlocked, r.ID.isBlob) + l.Log.Warn("incompatible tx in txpool", "id", r.ID, "is_blob", r.ID.isBlob) + } else if r.ID.isCancel && l.txpoolState == TxpoolCancelPending { + // Set state to TxpoolGood even if the cancellation transaction ended in error + // since the stuck transaction could have cleared while we were waiting. + l.setTxPoolState(TxpoolGood, l.txpoolBlockedBlob) + l.Log.Info("txpool may no longer be blocked", "err", r.Err) + } + l.Log.Info("Handling receipt", "id", r.ID) + l.handleReceipt(r) + case <-ctx.Done(): + l.Log.Info("Receipt processing loop done") + return + } + } +} + +// throttlingLoop monitors the backlog in bytes we need to make available, and appropriately enables or disables +// throttling of incoming data prevent the backlog from growing too large. By looping & calling the miner API setter +// continuously, we ensure the engine currently in use is always going to be reset to the proper throttling settings +// even in the event of sequencer failover. +func (l *BatchSubmitter) throttlingLoop(ctx context.Context) { + defer l.wg.Done() + l.Log.Info("Starting DA throttling loop") + ticker := time.NewTicker(l.Config.ThrottleInterval) + defer ticker.Stop() + + updateParams := func() { + ctx, cancel := context.WithTimeout(l.shutdownCtx, l.Config.NetworkTimeout) + defer cancel() + cl, err := l.EndpointProvider.EthClient(ctx) + if err != nil { + l.Log.Error("Can't reach sequencer execution RPC", "err", err) + return + } + pendingBytes := l.state.PendingDABytes() + maxTxSize := uint64(0) + maxBlockSize := l.Config.ThrottleAlwaysBlockSize + if pendingBytes > int64(l.Config.ThrottleThreshold) { + l.Log.Warn("Pending bytes over limit, throttling DA", "bytes", pendingBytes, "limit", l.Config.ThrottleThreshold) + maxTxSize = l.Config.ThrottleTxSize + if maxBlockSize == 0 || (l.Config.ThrottleBlockSize != 0 && l.Config.ThrottleBlockSize < maxBlockSize) { + maxBlockSize = l.Config.ThrottleBlockSize + } + } + var ( + success bool + rpcErr rpc.Error + ) + if err := cl.Client().CallContext( + ctx, &success, SetMaxDASizeMethod, hexutil.Uint64(maxTxSize), hexutil.Uint64(maxBlockSize), + ); errors.As(err, &rpcErr) && eth.ErrorCode(rpcErr.ErrorCode()).IsGenericRPCError() { + l.Log.Error("SetMaxDASize rpc unavailable or broken, shutting down. Either enable it or disable throttling.", "err", err) + // We'd probably hit this error right after startup, so a short shutdown duration should suffice. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + // Call StopBatchSubmitting in another goroutine to avoid deadlock. + go func() { + // Always returns nil. An error is only returned to expose this function as an RPC. + _ = l.StopBatchSubmitting(ctx) + }() + return + } else if err != nil { + l.Log.Error("SetMaxDASize rpc failed, retrying.", "err", err) + return + } + if !success { + l.Log.Error("Result of SetMaxDASize was false, retrying.") + } + } + + for { + select { + case <-l.l2BlockAdded: + updateParams() + case <-ticker.C: + updateParams() + case <-ctx.Done(): + l.Log.Info("DA throttling loop done") return } } } +func (l *BatchSubmitter) waitNodeSyncAndClearState() { + // Wait for any in flight transactions + // to be ingested by the node before + // we start loading blocks again. + err := l.waitNodeSync() + if err != nil { + l.Log.Warn("error waiting for node sync", "err", err) + } + l.clearState(l.shutdownCtx) +} + // waitNodeSync Check to see if there was a batcher tx sent recently that // still needs more block confirmations before being considered finalized func (l *BatchSubmitter) waitNodeSync() error { @@ -461,9 +630,11 @@ func (l *BatchSubmitter) waitNodeSync() error { return dial.WaitRollupSync(l.shutdownCtx, l.Log, rollupClient, l1TargetBlock, time.Second*12) } -// 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 queing the data. -func (l *BatchSubmitter) publishStateToL1(queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group) { +// 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 queing the data. maxDuration tells this function to return from state +// publishing after this amount of time has been exceeded even if there is more data remaining. +func (l *BatchSubmitter) publishStateToL1(queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group, maxDuration time.Duration) { + start := time.Now() for { // if the txmgr is closed, we stop the transaction sending if l.Txmgr.IsClosed() { @@ -481,6 +652,10 @@ func (l *BatchSubmitter) publishStateToL1(queue *txmgr.Queue[txRef], receiptsCh } return } + if time.Since(start) > maxDuration { + l.Log.Warn("Aborting state publishing, max duration exceeded") + return + } } } @@ -531,7 +706,7 @@ func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[t l.Log.Error("Failed to query L1 tip", "err", err) return err } - l.recordL1Tip(l1tip) + l.Metr.RecordLatestL1Block(l1tip) // 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. @@ -609,10 +784,16 @@ func (l *BatchSubmitter) publishToAltDAAndL1(txdata txData, queue *txmgr.Queue[t // So we prefer to mimic the behavior of txmgr and cancel all pending DA/txmgr requests when the batcher is stopped. comm, err := l.AltDA.SetInput(l.shutdownCtx, txdata.CallData()) if err != nil { - l.Log.Error("Failed to post input to Alt DA", "error", err) - // requeue frame if we fail to post to the DA Provider so it can be retried - // note: this assumes that the da server caches requests, otherwise it might lead to resubmissions of the blobs - l.recordFailedDARequest(txdata.ID(), err) + // Don't log context cancelled events because they are expected, + // and can happen after tests complete which causes a panic. + if errors.Is(err, context.Canceled) { + l.recordFailedDARequest(txdata.ID(), nil) + } else { + l.Log.Error("Failed to post input to Alt DA", "error", err) + // requeue frame if we fail to post to the DA Provider so it can be retried + // note: this assumes that the da server caches requests, otherwise it might lead to resubmissions of the blobs + l.recordFailedDARequest(txdata.ID(), err) + } return nil } l.Log.Info("Set altda input", "commitment", comm, "tx", txdata.ID()) @@ -658,6 +839,10 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txRef candidate = l.calldataTxCandidate(txdata.CallData()) } + if *candidate.To != l.RollupConfig.BatchInboxAddress { + return fmt.Errorf("candidate.To is not inbox") + } + l.sendTx(txdata, false, candidate, queue, receiptsCh) return nil } @@ -665,12 +850,39 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txRef // sendTx uses the txmgr queue to send the given transaction candidate after setting its // gaslimit. It will block if the txmgr queue has reached its MaxPendingTransactions limit. func (l *BatchSubmitter) sendTx(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue *txmgr.Queue[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { - intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false) - if err != nil { - // we log instead of return an error here because txmgr can do its own gas estimation - l.Log.Error("Failed to calculate intrinsic gas", "err", err) - } else { - candidate.GasLimit = intrinsicGas + var isEOAPointer *bool + if l.RollupConfig.UseInboxContract() { + // RollupConfig.UseInboxContract() being true just means the batcher's transaction status matters, + // but the actual inbox may still be an EOA. + isEOAPointer = l.inboxIsEOA.Load() + if isEOAPointer == nil { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + var code []byte + code, err := l.L1Client.CodeAt(ctx, *candidate.To, nil) + if err != nil { + l.Log.Error("CodeAt failed, assuming code exists", "err", err) + // assume code exist, but don't persist the result + isEOA := false + isEOAPointer = &isEOA + } else { + isEOA := len(code) == 0 + isEOAPointer = &isEOA + l.inboxIsEOA.Store(isEOAPointer) + } + } + } + + // Set GasLimit as intrinstic gas if the inbox is EOA, otherwise + // Leave GasLimit unset when inbox is contract so that later on `EstimateGas` will be called + if !l.RollupConfig.UseInboxContract() || *isEOAPointer { + intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false) + if err != nil { + // we log instead of return an error here because txmgr can do its own gas estimation + l.Log.Error("Failed to calculate intrinsic gas", "err", err) + } else { + candidate.GasLimit = intrinsicGas + } } queue.Send(txRef{id: txdata.ID(), isCancel: isCancel, isBlob: txdata.asBlob}, *candidate, receiptsCh) @@ -705,16 +917,14 @@ func (l *BatchSubmitter) handleReceipt(r txmgr.TxReceipt[txRef]) { if r.Err != nil { l.recordFailedTx(r.ID.id, r.Err) } else { - l.recordConfirmedTx(r.ID.id, r.Receipt) - } -} + // check tx status + if l.RollupConfig.UseInboxContract() && r.Receipt.Status == types.ReceiptStatusFailed { + l.recordFailedTx(r.ID.id, ErrInboxTransactionFailed) + return + } -func (l *BatchSubmitter) recordL1Tip(l1tip eth.L1BlockRef) { - if l.lastL1Tip == l1tip { - return + l.recordConfirmedTx(r.ID.id, r.Receipt) } - l.lastL1Tip = l1tip - l.Metr.RecordLatestL1Block(l1tip) } func (l *BatchSubmitter) recordFailedDARequest(id txID, err error) { @@ -725,6 +935,9 @@ func (l *BatchSubmitter) recordFailedDARequest(id txID, err error) { } func (l *BatchSubmitter) recordFailedTx(id txID, err error) { + if l.RollupConfig.UseInboxContract() { + l.inboxIsEOA.Store(nil) + } l.Log.Warn("Transaction failed to send", logFields(id, err)...) l.state.TxFailed(id) } diff --git a/op-batcher/batcher/driver_test.go b/op-batcher/batcher/driver_test.go index 5ce0983bfe1a7..f4e39e22cee2e 100644 --- a/op-batcher/batcher/driver_test.go +++ b/op-batcher/batcher/driver_test.go @@ -86,7 +86,6 @@ func TestBatchSubmitter_SafeL1Origin(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.failsToFetchSyncStatus { ep.rollupClient.ExpectSyncStatus(ð.SyncStatus{}, errors.New("failed to fetch sync status")) - } else { ep.rollupClient.ExpectSyncStatus(ð.SyncStatus{ SafeL2: eth.L2BlockRef{ @@ -107,7 +106,6 @@ func TestBatchSubmitter_SafeL1Origin(t *testing.T) { } }) } - } func TestBatchSubmitter_SafeL1Origin_FailsToResolveRollupClient(t *testing.T) { diff --git a/op-batcher/batcher/service.go b/op-batcher/batcher/service.go index 6ed906af15aa1..1b150642261f2 100644 --- a/op-batcher/batcher/service.go +++ b/op-batcher/batcher/service.go @@ -44,6 +44,11 @@ type BatcherConfig struct { WaitNodeSync bool CheckRecentTxsDepth int + + // For throttling DA. See CLIConfig in config.go for details on these parameters. + ThrottleThreshold, ThrottleTxSize uint64 + ThrottleBlockSize, ThrottleAlwaysBlockSize uint64 + ThrottleInterval time.Duration } // BatcherService represents a full batch-submitter instance and its resources, @@ -75,18 +80,20 @@ type BatcherService struct { NotSubmittingOnStart bool } +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) (*BatcherService, error) { +func BatcherServiceFromCLIConfig(ctx context.Context, version string, cfg *CLIConfig, log log.Logger, opts ...DriverSetupOption) (*BatcherService, error) { var bs BatcherService - if err := bs.initFromCLIConfig(ctx, version, cfg, log); err != nil { + if err := bs.initFromCLIConfig(ctx, 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) error { +func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, cfg *CLIConfig, log log.Logger, opts ...DriverSetupOption) error { bs.Version = version bs.Log = log bs.NotSubmittingOnStart = cfg.Stopped @@ -99,6 +106,13 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, bs.NetworkTimeout = cfg.TxMgrConfig.NetworkTimeout bs.CheckRecentTxsDepth = cfg.CheckRecentTxsDepth bs.WaitNodeSync = cfg.WaitNodeSync + + bs.ThrottleThreshold = cfg.ThrottleThreshold + bs.ThrottleTxSize = cfg.ThrottleTxSize + bs.ThrottleBlockSize = cfg.ThrottleBlockSize + bs.ThrottleAlwaysBlockSize = cfg.ThrottleAlwaysBlockSize + bs.ThrottleInterval = cfg.ThrottleInterval + if err := bs.initRPCClients(ctx, cfg); err != nil { return err } @@ -122,7 +136,7 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, if err := bs.initPProf(cfg); err != nil { return fmt.Errorf("failed to init profiling: %w", err) } - bs.initDriver() + bs.initDriver(opts...) if err := bs.initRPCServer(cfg); err != nil { return fmt.Errorf("failed to start RPC server: %w", err) } @@ -315,8 +329,8 @@ func (bs *BatcherService) initMetricsServer(cfg *CLIConfig) error { return nil } -func (bs *BatcherService) initDriver() { - bs.driver = NewBatchSubmitter(DriverSetup{ +func (bs *BatcherService) initDriver(opts ...DriverSetupOption) { + ds := DriverSetup{ Log: bs.Log, Metr: bs.Metrics, RollupConfig: bs.RollupConfig, @@ -326,7 +340,11 @@ func (bs *BatcherService) initDriver() { EndpointProvider: bs.EndpointProvider, ChannelConfig: bs.ChannelConfig, AltDA: bs.AltDA, - }) + } + for _, opt := range opts { + opt(&ds) + } + bs.driver = NewBatchSubmitter(ds) } func (bs *BatcherService) initRPCServer(cfg *CLIConfig) error { @@ -451,3 +469,13 @@ func (bs *BatcherService) TestDriver() *TestBatchSubmitter { BatchSubmitter: bs.driver, } } + +// ThrottlingTestDriver returns a handler for the batch-submitter driver element that is in "always throttle" mode, for +// use only in testing. +func (bs *BatcherService) ThrottlingTestDriver() *TestBatchSubmitter { + tbs := &TestBatchSubmitter{ + BatchSubmitter: bs.driver, + } + tbs.BatchSubmitter.state.metr = new(metrics.ThrottlingMetrics) + return tbs +} diff --git a/op-batcher/cmd/main.go b/op-batcher/cmd/main.go index 82472006da279..39ca58b193c3e 100644 --- a/op-batcher/cmd/main.go +++ b/op-batcher/cmd/main.go @@ -18,7 +18,7 @@ import ( ) var ( - Version = "v0.10.14" + Version = "v0.0.0" GitCommit = "" GitDate = "" ) diff --git a/op-batcher/flags/flags.go b/op-batcher/flags/flags.go index 3fe66f33981be..d5681ea872344 100644 --- a/op-batcher/flags/flags.go +++ b/op-batcher/flags/flags.go @@ -156,6 +156,36 @@ var ( Value: false, EnvVars: prefixEnvVars("WAIT_NODE_SYNC"), } + ThrottleIntervalFlag = &cli.DurationFlag{ + Name: "throttle-interval", + Usage: "Interval between potential DA throttling actions. Zero disables throttling.", + Value: 2 * time.Second, + EnvVars: prefixEnvVars("THROTTLE_INTERVAL"), + } + ThrottleThresholdFlag = &cli.IntFlag{ + Name: "throttle-threshold", + Usage: "The threshold on pending-blocks-bytes-current beyond which the batcher will instruct the block builder to start throttling transactions with larger DA demands", + Value: 1_000_000, + EnvVars: prefixEnvVars("THROTTLE_THRESHOLD"), + } + ThrottleTxSizeFlag = &cli.IntFlag{ + Name: "throttle-tx-size", + Usage: "The DA size of transactions to start throttling when we are over the throttle threshold", + Value: 300, // most transactions compress to under 300 bytes. TODO: compute exact distribution + EnvVars: prefixEnvVars("THROTTLE_TX_SIZE"), + } + ThrottleBlockSizeFlag = &cli.IntFlag{ + Name: "throttle-block-size", + Usage: "The total DA limit to start imposing on block building when we are over the throttle threshold", + Value: 21_000, // at least 70 transactions per block of up to 300 compressed bytes each. + EnvVars: prefixEnvVars("THROTTLE_BLOCK_SIZE"), + } + ThrottleAlwaysBlockSizeFlag = &cli.IntFlag{ + Name: "throttle-always-block-size", + Usage: "The total DA limit to start imposing on block building at all times", + Value: 130_000, // should be larger than the builder's max-l2-tx-size to prevent endlessly throttling some txs + EnvVars: prefixEnvVars("THROTTLE_ALWAYS_BLOCK_SIZE"), + } // Legacy Flags SequencerHDPathFlag = txmgr.SequencerHDPathFlag ) @@ -184,6 +214,11 @@ var optionalFlags = []cli.Flag{ DataAvailabilityTypeFlag, ActiveSequencerCheckDurationFlag, CompressionAlgoFlag, + ThrottleThresholdFlag, + ThrottleIntervalFlag, + ThrottleTxSizeFlag, + ThrottleBlockSizeFlag, + ThrottleAlwaysBlockSizeFlag, } func init() { diff --git a/op-batcher/justfile b/op-batcher/justfile new file mode 100644 index 0000000000000..a0c671ebd28a4 --- /dev/null +++ b/op-batcher/justfile @@ -0,0 +1,36 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X main.Version=" + VERSION + " " + \ + "") + "'" + +BINARY := "./bin/op-batcher" + +# Build op-batcher binary +op-batcher: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") + +[private] +batcher_fuzz_task FUZZ TIME='10s': (go_fuzz FUZZ TIME "./batcher") + +# Run fuzzing tests +fuzz: + printf "%s\n" \ + "FuzzChannelConfig_CheckTimeout" \ + "FuzzDurationZero" \ + "FuzzDurationTimeoutMaxChannelDuration" \ + "FuzzDurationTimeoutZeroMaxChannelDuration" \ + "FuzzChannelCloseTimeout" \ + "FuzzChannelZeroCloseTimeout" \ + "FuzzSeqWindowClose" \ + "FuzzSeqWindowZeroTimeoutClose" \ + | parallel -j {{PARALLEL_JOBS}} {{just_executable()}} batcher_fuzz_task {} diff --git a/op-batcher/metrics/metrics.go b/op-batcher/metrics/metrics.go index 417f927ee6f61..6443e489bd465 100644 --- a/op-batcher/metrics/metrics.go +++ b/op-batcher/metrics/metrics.go @@ -2,6 +2,7 @@ package metrics import ( "io" + "sync/atomic" "github.com/prometheus/client_golang/prometheus" @@ -49,6 +50,8 @@ type Metricer interface { RecordBlobUsedBytes(num int) Document() []opmetrics.DocumentedMetric + + PendingDABytes() float64 } type Metrics struct { @@ -69,7 +72,11 @@ type Metrics struct { pendingBlocksCount prometheus.GaugeVec pendingBlocksBytesTotal prometheus.Counter pendingBlocksBytesCurrent prometheus.Gauge - blocksAddedCount prometheus.Gauge + + pendingDABytes int64 + pendingDABytesGaugeFunc prometheus.GaugeFunc + + blocksAddedCount prometheus.Gauge channelInputBytes prometheus.GaugeVec channelReadyBytes prometheus.Gauge @@ -99,7 +106,7 @@ func NewMetrics(procName string) *Metrics { registry := opmetrics.NewRegistry() factory := opmetrics.With(registry) - return &Metrics{ + m := &Metrics{ ns: ns, registry: registry, factory: factory, @@ -143,7 +150,6 @@ func NewMetrics(procName string) *Metrics { Name: "blocks_added_count", Help: "Total number of blocks added to current channel.", }), - channelInputBytes: *factory.NewGaugeVec(prometheus.GaugeOpts{ Namespace: ns, Name: "input_bytes", @@ -194,6 +200,13 @@ func NewMetrics(procName string) *Metrics { batcherTxEvs: opmetrics.NewEventVec(factory, ns, "", "batcher_tx", "BatcherTx", []string{"stage"}), } + m.pendingDABytesGaugeFunc = factory.NewGaugeFunc(prometheus.GaugeOpts{ + Namespace: ns, + Name: "pending_da_bytes", + Help: "The estimated amount of data currently pending to be written to the DA layer (from blocks fetched from L2 but not yet in a channel).", + }, m.PendingDABytes) + + return m } func (m *Metrics) Registry() *prometheus.Registry { @@ -204,6 +217,12 @@ func (m *Metrics) Document() []opmetrics.DocumentedMetric { return m.factory.Document() } +// PendingDABytes returns the current number of bytes pending to be written to the DA layer (from blocks fetched from L2 +// but not yet in a channel). +func (m *Metrics) PendingDABytes() float64 { + return float64(atomic.LoadInt64(&m.pendingDABytes)) +} + func (m *Metrics) StartBalanceMetrics(l log.Logger, client *ethclient.Client, account common.Address) io.Closer { return opmetrics.LaunchBalanceMetrics(l, m.registry, m.ns, client, account) } @@ -278,14 +297,16 @@ func (m *Metrics) RecordChannelClosed(id derive.ChannelID, numPendingBlocks int, } func (m *Metrics) RecordL2BlockInPendingQueue(block *types.Block) { - size := float64(estimateBatchSize(block)) - m.pendingBlocksBytesTotal.Add(size) - m.pendingBlocksBytesCurrent.Add(size) + daSize, rawSize := estimateBatchSize(block) + m.pendingBlocksBytesTotal.Add(float64(rawSize)) + m.pendingBlocksBytesCurrent.Add(float64(rawSize)) + atomic.AddInt64(&m.pendingDABytes, int64(daSize)) } func (m *Metrics) RecordL2BlockInChannel(block *types.Block) { - size := float64(estimateBatchSize(block)) - m.pendingBlocksBytesCurrent.Add(-1 * size) + daSize, rawSize := estimateBatchSize(block) + m.pendingBlocksBytesCurrent.Add(-1.0 * float64(rawSize)) + atomic.AddInt64(&m.pendingDABytes, -1*int64(daSize)) // Refer to RecordL2BlocksAdded to see the current + count of bytes added to a channel } @@ -318,16 +339,22 @@ func (m *Metrics) RecordBlobUsedBytes(num int) { m.blobUsedBytes.Observe(float64(num)) } -// estimateBatchSize estimates the size of the batch -func estimateBatchSize(block *types.Block) uint64 { - size := uint64(70) // estimated overhead of batch metadata +// estimateBatchSize returns the estimated size of the block in a batch both with compression ('daSize') and without +// ('rawSize'). +func estimateBatchSize(block *types.Block) (daSize, rawSize uint64) { + daSize = uint64(70) // estimated overhead of batch metadata + rawSize = uint64(70) for _, tx := range block.Transactions() { - // Don't include deposit transactions in the batch. + // Deposit transactions are not included in batches if tx.IsDepositTx() { continue } + bigSize := tx.RollupCostData().EstimatedDASize() + if bigSize.IsUint64() { // this should always be true, but if not just ignore + daSize += bigSize.Uint64() + } // Add 2 for the overhead of encoding the tx bytes in a RLP list - size += tx.Size() + 2 + rawSize += tx.Size() + 2 } - return size + return } diff --git a/op-batcher/metrics/noop.go b/op-batcher/metrics/noop.go index 36594efe47c77..92711ee883d93 100644 --- a/op-batcher/metrics/noop.go +++ b/op-batcher/metrics/noop.go @@ -2,6 +2,7 @@ package metrics import ( "io" + "math" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -46,3 +47,16 @@ func (*noopMetrics) RecordBlobUsedBytes(int) {} func (*noopMetrics) StartBalanceMetrics(log.Logger, *ethclient.Client, common.Address) io.Closer { return nil } +func (nm *noopMetrics) PendingDABytes() float64 { + return 0.0 +} + +// ThrottlingMetrics is a noopMetrics that always returns a max value for PendingDABytes, to use in testing batcher +// backlog throttling. +type ThrottlingMetrics struct { + noopMetrics +} + +func (nm *ThrottlingMetrics) PendingDABytes() float64 { + return math.MaxFloat64 +} diff --git a/op-batcher/metrics/test.go b/op-batcher/metrics/test.go new file mode 100644 index 0000000000000..76c365ea7e2b0 --- /dev/null +++ b/op-batcher/metrics/test.go @@ -0,0 +1,22 @@ +package metrics + +import ( + "github.com/ethereum/go-ethereum/core/types" +) + +type TestMetrics struct { + noopMetrics + PendingBlocksBytesCurrent float64 +} + +var _ Metricer = new(TestMetrics) + +func (m *TestMetrics) RecordL2BlockInPendingQueue(block *types.Block) { + _, rawSize := estimateBatchSize(block) + m.PendingBlocksBytesCurrent += float64(rawSize) + +} +func (m *TestMetrics) RecordL2BlockInChannel(block *types.Block) { + _, rawSize := estimateBatchSize(block) + m.PendingBlocksBytesCurrent -= float64(rawSize) +} diff --git a/op-batcher/readme.md b/op-batcher/readme.md new file mode 100644 index 0000000000000..ba547845f0999 --- /dev/null +++ b/op-batcher/readme.md @@ -0,0 +1,88 @@ +# op-batcher + +The `op-batcher` is responsible for ensuring data availability. See the [specs](https://specs.optimism.io/protocol/batcher.html). + + +## Interactions & Dependencies +The `op-batcher` works together with the [sequencer](../op-node/) (which it reads unsafe blocks from), the data availability layer (e.g. Layer 1 or an [Alt DA](../op-alt-da/) layer, which it posts data to), and the [derivation pipeline](../op-node/) (which reads the data from the DA layer and progresses the safe chain). + +It depends directly on some code shared with the derivation pipeline, namely the [`ChannelOut`](../op-node/rollup/derive/channel_out.go) implementation(s). It also depends directly on the shared [txmgr](../op-service/txmgr/) module. + +## Testing +The batcher has a suite of unit test which can be triggered by running +``` +go test ./... +``` +from this directory. There are also end-to-end tests in [`op-e2e`](../op-e2e/) which integrate the batcher. + +## Architecture + +The architecture of this batcher implementation is shown on the left side of the following diagram: + +![architecture](./architecture.png) + +Batch submitting (writing to the DA layer, in the middle of the diagram) works together with the derivation pipeline (on the right side of the diagram, reading from the DA layer) to progress the safe chain. + +The philosophy behind the current architecture is: +* Blocks, channels and frames are kept around for as long as they might be needed, and discarded as soon as they are not needed. They are not moved from one part of state to another. +* We retain block data in a strict order for as long as necessary. We only garbage collect frames, channels and blocks when the safe head moves sufficiently and those structures have done their job. +* When something goes wrong, we rewind the state cursors by the minimal amount we need to get going again. + + +### Happy path + +In the happy path, the batcher periodically: +1. Enqueues unsafe blocks and dequeues safe blocks from the sequencer to its internal state. +2. Enqueues a new channel, if necessary. +3. Processes some unprocessed blocks into the current channel, triggers the compression of the block data and the creation of frames. +4. Sends frames from the channel queue to the DA layer as (e.g. to Ethereum L1 as calldata or blob transactions). +5. If there is more transaction data to send, go to 2. Else wait for a tick and go to 1. + + +The `blockCursor` state variable tracks the next unprocessed block. +In each channel, the `frameCursor` tracks the next unsent frame. + + +### Reorgs +When an L2 unsafe reorg is detected, the batch submitter will reset its state, and wait for any in flight transactions to be ingested by the verifier nodes before starting work again. + +### Tx Failed +When a Tx fails, an asynchronous receipts handler is triggered. The channel from whence the Tx's frames came has its `frameCursor` rewound, so that all the frames can be resubmitted in order. + +### Channel Times Out +When a Tx is confirmed, an asynchronous receipts handler is triggered. We only update the batcher's state if the channel timed out on chain. In that case, the `blockCursor` is rewound to the first block added to that channel, and the channel queue is cleared out. This allows the batcher to start fresh building a new channel starting from the same block -- it does not need to refetch blocks from the sequencer. + +## Design Principles and Optimization Targets +At the current time, the batcher should be optimized for correctness, simplicity and robustness. It is considered preferable to prioritize these properties, even at the expense of other potentially desirable properties such as frugality. For example, it is preferable to have the batcher resubmit some data from time to time ("wasting" money on data availability costs) instead of avoiding that by e.g. adding some persistent state to the batcher. + +The batcher can almost always recover from unforeseen situations by being restarted. + + +Some complexity is permitted, however, for handling data availability switching, so that the batcher is not wasting money for longer periods of time. + +### Data Availability Backlog + +A chain can potentially experience an influx of large transactions whose data availability requirements exceed the total +throughput of the data availability layer. While this situation might resolve on its own in the long term through the +data availability pricing mechanism, in practice this feedback loop is too slow to prevent a very large backlog of data +from being produced, even at a relatively low cost to whomever is submitting the large transactions. In such +circumstances, the safe head can fall significantly behind the unsafe head, and the time between seeing a transaction +(and charging it a given L1 data fee) and actually posting the transaction to the data availability layer grows larger +and larger. Because DA costs can rise quickly during such an event, the batcher can end up paying far more to post the +transaction to the DA layer than what can be recovered from the transaction's data fee. + +To prevent a significant DA backlog, the batcher can instruct the block builder (via op-geth's miner RPC API) to impose +thresholds on the total DA requirements of a single block, and/or the maximum DA requirement of any single +transaction. In the happy case, the batcher instructs the block builder to impose a block-level DA limit of +OP_BATCHER_THROTTLE_ALWAYS_BLOCK_SIZE, and imposes no additional limit on the DA requirements of a single +transaction. But in the case of a DA backlog (as defined by OP_BATCHER_THROTTLE_THRESHOLD), the batcher instructs the +block builder to instead impose a (tighter) block level limit of OP_BATCHER_THROTTLE_BLOCK_SIZE, and a single +transaction limit of OP_BATCHER_THROTTLE_TRANSACTION_SIZE. + +## Known issues and future work + +Link to [open issues with the `op-batcher` tag](https://github.com/ethereum-optimism/optimism/issues?q=is%3Aopen+is%3Aissue+label%3AA-op-batcher). + +The batcher launches L1 transactions in parallel so that it can achieve higher throughput, particularly in situations where there is a large backlog of data which needs to be posted. Sometimes, transactions can get stuck in the L1 mempool. The batcher does have functionality to clear these stuck transactions, but it is not completely reliable. + +The automatic data availability switching behavior is a somewhat new feature which may still have some bugs in it. diff --git a/op-bootnode/.gitignore b/op-bootnode/.gitignore deleted file mode 100644 index ba077a4031add..0000000000000 --- a/op-bootnode/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bin diff --git a/op-bootnode/Makefile b/op-bootnode/Makefile deleted file mode 100644 index f9f276e9ed113..0000000000000 --- a/op-bootnode/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -VERSION ?= v0.0.0 - -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.Version=$(VERSION) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -op-bootnode: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/op-bootnode ./cmd - -clean: - rm -f bin/op-bootnode - -test: - go test -v ./... - -.PHONY: \ - op-bootnode \ - clean \ - test diff --git a/op-bootnode/bootnode/entrypoint.go b/op-bootnode/bootnode/entrypoint.go deleted file mode 100644 index 0c33383c70c7b..0000000000000 --- a/op-bootnode/bootnode/entrypoint.go +++ /dev/null @@ -1,134 +0,0 @@ -package bootnode - -import ( - "context" - "errors" - "fmt" - - "github.com/libp2p/go-libp2p/core/peer" - "github.com/urfave/cli/v2" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - - opnode "github.com/ethereum-optimism/optimism/op-node" - "github.com/ethereum-optimism/optimism/op-node/metrics" - "github.com/ethereum-optimism/optimism/op-node/p2p" - p2pcli "github.com/ethereum-optimism/optimism/op-node/p2p/cli" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" - "github.com/ethereum-optimism/optimism/op-service/eth" - oplog "github.com/ethereum-optimism/optimism/op-service/log" - opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" - oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" -) - -type gossipNoop struct{} - -func (g *gossipNoop) OnUnsafeL2Payload(_ context.Context, _ peer.ID, _ *eth.ExecutionPayloadEnvelope) error { - return nil -} - -type gossipConfig struct{} - -func (g *gossipConfig) P2PSequencerAddress() common.Address { - return common.Address{} -} - -type l2Chain struct{} - -func (l *l2Chain) PayloadByNumber(_ context.Context, _ uint64) (*eth.ExecutionPayloadEnvelope, error) { - return nil, errors.New("P2P req/resp is not supported in bootnodes") -} - -func Main(cliCtx *cli.Context) error { - log.Info("Initializing bootnode") - logCfg := oplog.ReadCLIConfig(cliCtx) - logger := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) - oplog.SetGlobalLogHandler(logger.Handler()) - m := metrics.NewMetrics("default") - ctx := context.Background() - - config, err := opnode.NewRollupConfigFromCLI(logger, cliCtx) - if err != nil { - return err - } - if err = validateConfig(config); err != nil { - return err - } - - p2pConfig, err := p2pcli.NewConfig(cliCtx, config) - if err != nil { - return fmt.Errorf("failed to load p2p config: %w", err) - } - if p2pConfig.EnableReqRespSync { - logger.Warn("req-resp sync is enabled, bootnode does not support this feature") - p2pConfig.EnableReqRespSync = false - } - - p2pNode, err := p2p.NewNodeP2P(ctx, config, logger, p2pConfig, &gossipNoop{}, &l2Chain{}, &gossipConfig{}, m, false) - if err != nil || p2pNode == nil { - return err - } - if p2pNode.Dv5Udp() == nil { - return fmt.Errorf("uninitialized discovery service") - } - - rpcCfg := oprpc.ReadCLIConfig(cliCtx) - if err := rpcCfg.Check(); err != nil { - return fmt.Errorf("failed to validate RPC config") - } - rpcServer := oprpc.NewServer(rpcCfg.ListenAddr, rpcCfg.ListenPort, "", oprpc.WithLogger(logger)) - if rpcCfg.EnableAdmin { - logger.Info("Admin RPC enabled but does nothing for the bootnode") - } - rpcServer.AddAPI(rpc.API{ - Namespace: p2p.NamespaceRPC, - Version: "", - Service: p2p.NewP2PAPIBackend(p2pNode, logger, m), - Authenticated: false, - }) - if err := rpcServer.Start(); err != nil { - return fmt.Errorf("failed to start the RPC server") - } - defer func() { - if err := rpcServer.Stop(); err != nil { - log.Error("failed to stop RPC server", "err", err) - } - }() - - go p2pNode.DiscoveryProcess(ctx, logger, config, p2pConfig.TargetPeers()) - - metricsCfg := opmetrics.ReadCLIConfig(cliCtx) - if metricsCfg.Enabled { - log.Debug("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) - metricsSrv, err := m.StartServer(metricsCfg.ListenAddr, metricsCfg.ListenPort) - if err != nil { - return fmt.Errorf("failed to start metrics server: %w", err) - } - defer func() { - if err := metricsSrv.Stop(context.Background()); err != nil { - log.Error("failed to stop metrics server", "err", err) - } - }() - log.Info("started metrics server", "addr", metricsSrv.Addr()) - m.RecordUp() - } - - return ctxinterrupt.Wait(ctx) -} - -// validateConfig ensures the minimal config required to run a bootnode -func validateConfig(config *rollup.Config) error { - if config.L2ChainID == nil || config.L2ChainID.Uint64() == 0 { - return errors.New("chain ID is not set") - } - if config.Genesis.L2Time <= 0 { - return errors.New("genesis timestamp is not set") - } - if config.BlockTime <= 0 { - return errors.New("block time is not set") - } - return nil -} diff --git a/op-bootnode/cmd/main.go b/op-bootnode/cmd/main.go deleted file mode 100644 index 1dbc82010cfc1..0000000000000 --- a/op-bootnode/cmd/main.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "os" - - "github.com/ethereum/go-ethereum/log" - "github.com/urfave/cli/v2" - - "github.com/ethereum-optimism/optimism/op-bootnode/bootnode" - "github.com/ethereum-optimism/optimism/op-bootnode/flags" - oplog "github.com/ethereum-optimism/optimism/op-service/log" -) - -func main() { - oplog.SetupDefaults() - - app := cli.NewApp() - app.Flags = flags.Flags - app.Name = "bootnode" - app.Usage = "Rollup Bootnode" - app.Description = "Broadcasts incoming P2P peers to each other, enabling peer bootstrapping." - app.Action = bootnode.Main - - err := app.Run(os.Args) - if err != nil { - log.Crit("Application failed", "message", err) - } -} diff --git a/op-bootnode/flags/flags.go b/op-bootnode/flags/flags.go deleted file mode 100644 index 5fbe0d538b33c..0000000000000 --- a/op-bootnode/flags/flags.go +++ /dev/null @@ -1,25 +0,0 @@ -package flags - -import ( - "github.com/urfave/cli/v2" - - "github.com/ethereum-optimism/optimism/op-node/flags" - opflags "github.com/ethereum-optimism/optimism/op-service/flags" - oplog "github.com/ethereum-optimism/optimism/op-service/log" - opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" - oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" -) - -const envVarPrefix = "OP_BOOTNODE" - -var Flags = []cli.Flag{ - opflags.CLINetworkFlag(envVarPrefix, ""), - opflags.CLIRollupConfigFlag(envVarPrefix, ""), -} - -func init() { - Flags = append(Flags, flags.P2PFlags(envVarPrefix)...) - Flags = append(Flags, opmetrics.CLIFlags(envVarPrefix)...) - Flags = append(Flags, oplog.CLIFlags(envVarPrefix)...) - Flags = append(Flags, oprpc.CLIFlags(envVarPrefix)...) -} diff --git a/op-chain-ops/Makefile b/op-chain-ops/Makefile index 6c4b576528554..4dac7c1b6bb77 100644 --- a/op-chain-ops/Makefile +++ b/op-chain-ops/Makefile @@ -1,52 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') +DEPRECATED_TARGETS := ecotone-scalar receipt-reference-builder test op-deployer fuzz sync-standard-version -# Find the github tag that points to this commit. If none are found, set the version string to "untagged" -# Prioritizes release tag, if one exists, over tags suffixed with "-rc" -VERSION ?= $(shell tags=$$(git tag --points-at $(GITCOMMIT) | grep '^op-deployer/' | sed 's/op-deployer\///' | sort -V); \ - preferred_tag=$$(echo "$$tags" | grep -v -- '-rc' | tail -n 1); \ - if [ -z "$$preferred_tag" ]; then \ - if [ -z "$$tags" ]; then \ - echo "untagged"; \ - else \ - echo "$$tags" | tail -n 1; \ - fi \ - else \ - echo $$preferred_tag; \ - fi) - -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Version=$(VERSION) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Meta=$(VERSION_META) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -# Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169 -ifeq ($(shell uname),Darwin) - FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic -endif - -ecotone-scalar: - go build -o ./bin/ecotone-scalar ./cmd/ecotone-scalar/main.go - -receipt-reference-builder: - go build -o ./bin/receipt-reference-builder ./cmd/receipt-reference-builder/*.go - -test: - go test ./... - -op-deployer: - GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-deployer ./cmd/op-deployer/main.go - -fuzz: - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzEncodeDecodeWithdrawal ./crossdomain - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzEncodeDecodeLegacyWithdrawal ./crossdomain - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzAliasing ./crossdomain - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzVersionedNonce ./crossdomain - - -sync-standard-version: - curl -Lo ./deployer/opcm/standard-versions-mainnet.toml https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/validation/standard/standard-versions-mainnet.toml - curl -Lo ./deployer/opcm/standard-versions-sepolia.toml https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/validation/standard/standard-versions-sepolia.toml - -.PHONY: test fuzz op-deployer sync-standard-version \ No newline at end of file +include ../just/deprecated.mk diff --git a/op-chain-ops/README.md b/op-chain-ops/README.md new file mode 100644 index 0000000000000..98d0d974722d9 --- /dev/null +++ b/op-chain-ops/README.md @@ -0,0 +1,80 @@ +# `op-chain-ops` + +Issues: [monorepo](https://github.com/ethereum-optimism/optimism/issues?q=is%3Aissue%20state%3Aopen%20label%3AA-op-chain-ops) + +Pull requests: [monorepo](https://github.com/ethereum-optimism/optimism/pulls?q=is%3Aopen+is%3Apr+label%3AA-op-chain-ops) + +This is an OP Stack utils package for chain operations, +ranging from EVM tooling to chain generation. + +Packages: +- `clients`: utils for chain checker tools. +- `cmd`: upgrade validation tools, debug tools, attributes formatting tools. +- `crossdomain`: utils to interact with L1 <> L2 cross-domain messages. +- `devkeys`: generate OP-Stack development keys from a common source. +- `foundry`: utils to read foundry artifacts. +- `genesis`: OP Stack genesis-configs generation, pre OPCM. +- `interopgen`: interop test-chain genesis config generation. +- `script`: foundry-like solidity scripting environment in Go. +- `solc`: utils to read solidity compiler artifacts data. +- `srcmap`: utils for solidity source-maps loaded from foundry-artifacts. + +## Usage + +Upgrade checks and chain utilities can be found in `./cmd`: +these are not officially published in OP-Stack monorepo releases, +but can be built from source. + +Utils: +```text +cmd/ +├── check-canyon - Checks for Canyon network upgrade +├── check-delta - Checks for Delta network upgrade +├── check-deploy-config - Checks of the (legacy) Deploy Config +├── check-derivation - Check that transactions can be confirmed and safety can be consolidated +├── check-ecotone - Checks for Ecotone network upgrade +├── check-fjord - Checks for Fjord network upgrade +├── deposit-hash - Determine the L2 deposit tx hash, based on log event(s) emitted by a L1 tx. +├── ecotone-scalar - Translate between serialized and human-readable L1 fee scalars (introduced in Ecotone upgrade). +├── op-simulate - Simulate a remote transaction in a local Geth EVM for block-processing debugging. +├── protocol-version - Translate between serialized and human-readable protocol versions. +├── receipt-reference-builder - Receipt data collector for pre-Canyon deposit-nonce metadata. +└── unclaimed-credits - Utilitiy to inspect credits of resolved fault-proof games. +``` + +## Product + +### Optimization target + +Provide tools for chain-setup and inspection tools for deployment, upgrades, and testing. +This includes `op-deployer`, OP-Contracts-Manager (OPCM), upgrade-check scripts, and `op-e2e` testing. + +### Vision + +- Upgrade checking scripts should become more extensible, and maybe be bundled in a single check-script CLI tool. +- Serve chain inspection/processing building-blocks for test setups and tooling like op-deployer. +- `interopgen` is meant to be temporary, and consolidate with `op-deployer`. + This change depends largely on the future of `op-e2e`, + where system tests may be replaced in favor of tests set up by `op-e2e`. +- `script` is a Go version of `forge` script, with hooks and customization options, + for better integration into tooling such as `op-deployer`. + This package should evolve to serve testing and `op-deployer` as best as possible, + it is not a full `forge` replacement. +- `genesis` will shrink over time, as more of the genesis responsibilities are automated away into + the protocol through system-transactions, and tooling such as `op-deployer` and OPCM. + +## Design principles + +- Provide high-quality bindings to accelerate testing and tooling development. +- Minimal introspection into fragile solidity details. + +There is a trade-off here in how minimal the tooling is: +generally we aim to provide dedicated functionality in Go for better integration, +if the target tool is significant Go service of its own. +If not, then `op-chain-ops` should not be extended, and the design of the target tool should be adjusted instead. + +## Testing + +- Upgrade checks are tested against live devnet/testnet upgrades, before testing against mainnet. + Testing here is aimed to expand to end-to-end testing, for better integrated test feedback of these tools. +- Utils have unit-test coverage of their own, and are used widely in end-to-end testing itself. diff --git a/op-chain-ops/cmd/check-canyon/main.go b/op-chain-ops/cmd/check-canyon/main.go index deb5270ed639e..d0f3bdb89d0e5 100644 --- a/op-chain-ops/cmd/check-canyon/main.go +++ b/op-chain-ops/cmd/check-canyon/main.go @@ -232,7 +232,7 @@ func main() { // Parse the command-line arguments flag.Parse() - l2RPC, err := client.NewRPC(context.Background(), logger, rpcURL, client.WithDialBackoff(10)) + l2RPC, err := client.NewRPC(context.Background(), logger, rpcURL, client.WithDialAttempts(10)) if err != nil { log.Crit("Error creating RPC", "err", err) } diff --git a/op-chain-ops/cmd/deposit-hash/main.go b/op-chain-ops/cmd/deposit-hash/main.go new file mode 100644 index 0000000000000..9166a0667c090 --- /dev/null +++ b/op-chain-ops/cmd/deposit-hash/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "flag" + "fmt" + + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +func main() { + var rpcURL, txHash string + flag.StringVar(&rpcURL, "rpc", "", "L1 RPC URL") + flag.StringVar(&txHash, "tx", "", "Deposit transaction hash on L1") + flag.Parse() + + depositLogTopic := common.HexToHash("0xb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32") + + ethClient, err := ethclient.Dial(rpcURL) + if err != nil { + log.Crit("Error creating RPC", "err", err) + } + + l1Receipt, err := ethClient.TransactionReceipt(context.TODO(), common.HexToHash(txHash)) + if err != nil { + log.Crit("Error fetching transaction", "err", err) + } + + for _, ethLog := range l1Receipt.Logs { + if ethLog.Topics[0].String() == depositLogTopic.String() { + + reconstructedDep, err := derive.UnmarshalDepositLogEvent(ethLog) + if err != nil { + log.Crit("Failed to parse deposit event ", "err", err) + } + tx := types.NewTx(reconstructedDep) + fmt.Println("L2 Tx Hash", tx.Hash().String()) + } + } +} diff --git a/op-chain-ops/contracts/common.go b/op-chain-ops/contracts/common.go deleted file mode 100644 index a1eb5b471dbb5..0000000000000 --- a/op-chain-ops/contracts/common.go +++ /dev/null @@ -1,21 +0,0 @@ -package contracts - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/urfave/cli/v2" -) - -// parseAddress will parse a [common.Address] from a [cli.Context] and return -// an error if the configured address is not correct. -func parseAddress(ctx *cli.Context, name string) (common.Address, error) { - value := ctx.String(name) - if value == "" { - return common.Address{}, nil - } - if !common.IsHexAddress(value) { - return common.Address{}, fmt.Errorf("invalid address: %s", value) - } - return common.HexToAddress(value), nil -} diff --git a/op-chain-ops/contracts/contracts.go b/op-chain-ops/contracts/contracts.go deleted file mode 100644 index def9c3746201e..0000000000000 --- a/op-chain-ops/contracts/contracts.go +++ /dev/null @@ -1,50 +0,0 @@ -package contracts - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/urfave/cli/v2" -) - -// Addresses represents the address values of various contracts. The values can -// be easily populated via a [cli.Context]. -type Addresses struct { - AddressManager common.Address - OptimismPortal common.Address - L1StandardBridge common.Address - L1CrossDomainMessenger common.Address - CanonicalTransactionChain common.Address - StateCommitmentChain common.Address -} - -// NewAddresses populates an Addresses struct given a [cli.Context]. -// This is useful for writing scripts that interact with smart contracts. -func NewAddresses(ctx *cli.Context) (*Addresses, error) { - var addresses Addresses - var err error - - addresses.AddressManager, err = parseAddress(ctx, "address-manager-address") - if err != nil { - return nil, err - } - addresses.OptimismPortal, err = parseAddress(ctx, "optimism-portal-address") - if err != nil { - return nil, err - } - addresses.L1StandardBridge, err = parseAddress(ctx, "l1-standard-bridge-address") - if err != nil { - return nil, err - } - addresses.L1CrossDomainMessenger, err = parseAddress(ctx, "l1-crossdomain-messenger-address") - if err != nil { - return nil, err - } - addresses.CanonicalTransactionChain, err = parseAddress(ctx, "canonical-transaction-chain-address") - if err != nil { - return nil, err - } - addresses.StateCommitmentChain, err = parseAddress(ctx, "state-commitment-chain-address") - if err != nil { - return nil, err - } - return &addresses, nil -} diff --git a/op-chain-ops/deployer/apply.go b/op-chain-ops/deployer/apply.go deleted file mode 100644 index ca34d4266df79..0000000000000 --- a/op-chain-ops/deployer/apply.go +++ /dev/null @@ -1,189 +0,0 @@ -package deployer - -import ( - "context" - "crypto/ecdsa" - "fmt" - "strings" - - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" - opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" - "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" - oplog "github.com/ethereum-optimism/optimism/op-service/log" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/urfave/cli/v2" -) - -type ApplyConfig struct { - L1RPCUrl string - Workdir string - PrivateKey string - Logger log.Logger - - privateKeyECDSA *ecdsa.PrivateKey -} - -func (a *ApplyConfig) Check() error { - if a.L1RPCUrl == "" { - return fmt.Errorf("l1RPCUrl must be specified") - } - - if a.Workdir == "" { - return fmt.Errorf("workdir must be specified") - } - - if a.PrivateKey == "" { - return fmt.Errorf("private key must be specified") - } - - privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(a.PrivateKey, "0x")) - if err != nil { - return fmt.Errorf("failed to parse private key: %w", err) - } - a.privateKeyECDSA = privECDSA - - if a.Logger == nil { - return fmt.Errorf("logger must be specified") - } - - return nil -} - -func ApplyCLI() func(cliCtx *cli.Context) error { - return func(cliCtx *cli.Context) error { - logCfg := oplog.ReadCLIConfig(cliCtx) - l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) - oplog.SetGlobalLogHandler(l.Handler()) - - l1RPCUrl := cliCtx.String(L1RPCURLFlagName) - workdir := cliCtx.String(WorkdirFlagName) - privateKey := cliCtx.String(PrivateKeyFlagName) - - ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - - return Apply(ctx, ApplyConfig{ - L1RPCUrl: l1RPCUrl, - Workdir: workdir, - PrivateKey: privateKey, - Logger: l, - }) - } -} - -func Apply(ctx context.Context, cfg ApplyConfig) error { - if err := cfg.Check(); err != nil { - return fmt.Errorf("invalid config for apply: %w", err) - } - - l1Client, err := ethclient.Dial(cfg.L1RPCUrl) - if err != nil { - return fmt.Errorf("failed to connect to L1 RPC: %w", err) - } - - chainID, err := l1Client.ChainID(ctx) - if err != nil { - return fmt.Errorf("failed to get chain ID: %w", err) - } - - signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID)) - deployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey) - - env := &pipeline.Env{ - Workdir: cfg.Workdir, - L1Client: l1Client, - Logger: cfg.Logger, - Signer: signer, - Deployer: deployer, - } - - intent, err := env.ReadIntent() - if err != nil { - return err - } - - if err := intent.Check(); err != nil { - return fmt.Errorf("invalid intent: %w", err) - } - - st, err := env.ReadState() - if err != nil { - return err - } - - if err := ApplyPipeline(ctx, env, intent, st); err != nil { - return err - } - - st.AppliedIntent = intent - if err := env.WriteState(st); err != nil { - return err - } - - return nil -} - -type pipelineStage struct { - name string - apply pipeline.Stage -} - -func ApplyPipeline( - ctx context.Context, - env *pipeline.Env, - intent *state.Intent, - st *state.State, -) error { - progressor := func(curr, total int64) { - env.Logger.Info("artifacts download progress", "current", curr, "total", total) - } - - artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, intent.ContractArtifactsURL, progressor) - if err != nil { - return fmt.Errorf("failed to download artifacts: %w", err) - } - defer func() { - if err := cleanup(); err != nil { - env.Logger.Warn("failed to clean up artifacts", "err", err) - } - }() - - pline := []pipelineStage{ - {"init", pipeline.Init}, - {"deploy-superchain", pipeline.DeploySuperchain}, - {"deploy-implementations", pipeline.DeployImplementations}, - } - - for _, chain := range intent.Chains { - chainID := chain.ID - pline = append(pline, pipelineStage{ - fmt.Sprintf("deploy-opchain-%s", chainID.Hex()), - func(ctx context.Context, env *pipeline.Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { - return pipeline.DeployOPChain(ctx, env, artifactsFS, intent, st, chainID) - }, - }, pipelineStage{ - fmt.Sprintf("generate-l2-genesis-%s", chainID.Hex()), - func(ctx context.Context, env *pipeline.Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { - return pipeline.GenerateL2Genesis(ctx, env, artifactsFS, intent, st, chainID) - }, - }) - } - - for _, stage := range pline { - if err := stage.apply(ctx, env, artifactsFS, intent, st); err != nil { - return fmt.Errorf("error in pipeline stage apply: %w", err) - } - } - - st.AppliedIntent = intent - if err := env.WriteState(st); err != nil { - return fmt.Errorf("failed to write state: %w", err) - } - - return nil -} diff --git a/op-chain-ops/deployer/bootstrap/bootstrap.go b/op-chain-ops/deployer/bootstrap/bootstrap.go deleted file mode 100644 index 5f1fc7db254e5..0000000000000 --- a/op-chain-ops/deployer/bootstrap/bootstrap.go +++ /dev/null @@ -1,206 +0,0 @@ -package bootstrap - -import ( - "context" - "crypto/ecdsa" - "crypto/rand" - "fmt" - "math/big" - "strings" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/script" - opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" - "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" - "github.com/ethereum-optimism/optimism/op-service/ioutil" - "github.com/ethereum-optimism/optimism/op-service/jsonutil" - oplog "github.com/ethereum-optimism/optimism/op-service/log" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/urfave/cli/v2" -) - -type OPCMConfig struct { - L1RPCUrl string - PrivateKey string - Logger log.Logger - ArtifactsURL *state.ArtifactsURL - ContractsRelease string - - privateKeyECDSA *ecdsa.PrivateKey -} - -func (c *OPCMConfig) Check() error { - if c.L1RPCUrl == "" { - return fmt.Errorf("l1RPCUrl must be specified") - } - - if c.PrivateKey == "" { - return fmt.Errorf("private key must be specified") - } - - privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x")) - if err != nil { - return fmt.Errorf("failed to parse private key: %w", err) - } - c.privateKeyECDSA = privECDSA - - if c.Logger == nil { - return fmt.Errorf("logger must be specified") - } - - if c.ArtifactsURL == nil { - return fmt.Errorf("artifacts URL must be specified") - } - - if c.ContractsRelease == "" { - return fmt.Errorf("contracts release must be specified") - } - - return nil -} - -func OPCMCLI(cliCtx *cli.Context) error { - logCfg := oplog.ReadCLIConfig(cliCtx) - l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) - oplog.SetGlobalLogHandler(l.Handler()) - - l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) - privateKey := cliCtx.String(deployer.PrivateKeyFlagName) - artifactsURLStr := cliCtx.String(ArtifactsURLFlagName) - artifactsURL := new(state.ArtifactsURL) - if err := artifactsURL.UnmarshalText([]byte(artifactsURLStr)); err != nil { - return fmt.Errorf("failed to parse artifacts URL: %w", err) - } - contractsRelease := cliCtx.String(ContractsReleaseFlagName) - - ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) - - return OPCM(ctx, OPCMConfig{ - L1RPCUrl: l1RPCUrl, - PrivateKey: privateKey, - Logger: l, - ArtifactsURL: artifactsURL, - ContractsRelease: contractsRelease, - }) -} - -func OPCM(ctx context.Context, cfg OPCMConfig) error { - if err := cfg.Check(); err != nil { - return fmt.Errorf("invalid config for OPCM: %w", err) - } - - lgr := cfg.Logger - progressor := func(curr, total int64) { - lgr.Info("artifacts download progress", "current", curr, "total", total) - } - - artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, cfg.ArtifactsURL, progressor) - if err != nil { - return fmt.Errorf("failed to download artifacts: %w", err) - } - defer func() { - if err := cleanup(); err != nil { - lgr.Warn("failed to clean up artifacts", "err", err) - } - }() - - l1Client, err := ethclient.Dial(cfg.L1RPCUrl) - if err != nil { - return fmt.Errorf("failed to connect to L1 RPC: %w", err) - } - - chainID, err := l1Client.ChainID(ctx) - if err != nil { - return fmt.Errorf("failed to get chain ID: %w", err) - } - chainIDU64 := chainID.Uint64() - - superCfg, err := opcm.SuperchainFor(chainIDU64) - if err != nil { - return fmt.Errorf("error getting superchain config: %w", err) - } - standardVersionsTOML, err := opcm.StandardVersionsFor(chainIDU64) - if err != nil { - return fmt.Errorf("error getting standard versions TOML: %w", err) - } - opcmProxyOwnerAddr, err := opcm.ManagerOwnerAddrFor(chainIDU64) - if err != nil { - return fmt.Errorf("error getting superchain proxy admin: %w", err) - } - - signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID)) - chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey) - - lgr.Info("deploying OPCM", "release", cfg.ContractsRelease) - - var dio opcm.DeployImplementationsOutput - err = pipeline.CallScriptBroadcast( - ctx, - pipeline.CallScriptBroadcastOpts{ - L1ChainID: chainID, - Logger: lgr, - ArtifactsFS: artifactsFS, - Deployer: chainDeployer, - Signer: signer, - Client: l1Client, - Broadcaster: pipeline.KeyedBroadcaster, - Handler: func(host *script.Host) error { - // We need to etch the Superchain addresses so that they have nonzero code - // and the checks in the OPCM constructor pass. - superchainConfigAddr := common.Address(*superCfg.Config.SuperchainConfigAddr) - protocolVersionsAddr := common.Address(*superCfg.Config.ProtocolVersionsAddr) - addresses := []common.Address{ - superchainConfigAddr, - protocolVersionsAddr, - } - for _, addr := range addresses { - host.ImportAccount(addr, types.Account{ - Code: []byte{0x00}, - }) - } - - var salt common.Hash - _, err = rand.Read(salt[:]) - if err != nil { - return fmt.Errorf("failed to generate CREATE2 salt: %w", err) - } - - dio, err = opcm.DeployImplementations( - host, - opcm.DeployImplementationsInput{ - Salt: salt, - WithdrawalDelaySeconds: big.NewInt(604800), - MinProposalSizeBytes: big.NewInt(126000), - ChallengePeriodSeconds: big.NewInt(86400), - ProofMaturityDelaySeconds: big.NewInt(604800), - DisputeGameFinalityDelaySeconds: big.NewInt(302400), - Release: cfg.ContractsRelease, - SuperchainConfigProxy: superchainConfigAddr, - ProtocolVersionsProxy: protocolVersionsAddr, - OpcmProxyOwner: opcmProxyOwnerAddr, - StandardVersionsToml: standardVersionsTOML, - UseInterop: false, - }, - ) - return err - }, - }, - ) - if err != nil { - return fmt.Errorf("error deploying implementations: %w", err) - } - - lgr.Info("deployed implementations") - - if err := jsonutil.WriteJSON(dio, ioutil.ToStdOut()); err != nil { - return fmt.Errorf("failed to write output: %w", err) - } - return nil -} diff --git a/op-chain-ops/deployer/bootstrap/flags.go b/op-chain-ops/deployer/bootstrap/flags.go deleted file mode 100644 index edb784da9fce9..0000000000000 --- a/op-chain-ops/deployer/bootstrap/flags.go +++ /dev/null @@ -1,41 +0,0 @@ -package bootstrap - -import ( - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" - "github.com/ethereum-optimism/optimism/op-service/cliapp" - "github.com/urfave/cli/v2" -) - -const ( - ArtifactsURLFlagName = "artifacts-url" - ContractsReleaseFlagName = "contracts-release" -) - -var ( - ArtifactsURLFlag = &cli.StringFlag{ - Name: ArtifactsURLFlagName, - Usage: "URL to the artifacts directory.", - EnvVars: deployer.PrefixEnvVar("ARTIFACTS_URL"), - } - ContractsReleaseFlag = &cli.StringFlag{ - Name: ContractsReleaseFlagName, - Usage: "Release of the contracts to deploy.", - EnvVars: deployer.PrefixEnvVar("CONTRACTS_RELEASE"), - } -) - -var OPCMFlags = []cli.Flag{ - deployer.L1RPCURLFlag, - deployer.PrivateKeyFlag, - ArtifactsURLFlag, - ContractsReleaseFlag, -} - -var Commands = []*cli.Command{ - { - Name: "opcm", - Usage: "Bootstrap an instance of OPCM.", - Flags: cliapp.ProtectFlags(OPCMFlags), - Action: OPCMCLI, - }, -} diff --git a/op-chain-ops/deployer/init.go b/op-chain-ops/deployer/init.go deleted file mode 100644 index 239e1b2da4150..0000000000000 --- a/op-chain-ops/deployer/init.go +++ /dev/null @@ -1,143 +0,0 @@ -package deployer - -import ( - "errors" - "fmt" - "os" - "path" - "strings" - - op_service "github.com/ethereum-optimism/optimism/op-service" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" - "github.com/ethereum/go-ethereum/common" - "github.com/urfave/cli/v2" -) - -var V160ArtifactsURL = state.MustParseArtifactsURL("https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-ee07c78c3d8d4cd8f7a933c050f5afeebaa281b57b226cc6f092b19de2a8d61f.tar.gz") - -type InitConfig struct { - L1ChainID uint64 - Outdir string - L2ChainIDs []common.Hash -} - -func (c *InitConfig) Check() error { - if c.L1ChainID == 0 { - return fmt.Errorf("l1ChainID must be specified") - } - - if c.Outdir == "" { - return fmt.Errorf("outdir must be specified") - } - - if len(c.L2ChainIDs) == 0 { - return fmt.Errorf("must specify at least one L2 chain ID") - } - - return nil -} - -func InitCLI() func(ctx *cli.Context) error { - return func(ctx *cli.Context) error { - l1ChainID := ctx.Uint64(L1ChainIDFlagName) - outdir := ctx.String(OutdirFlagName) - - l2ChainIDsRaw := ctx.String(L2ChainIDsFlagName) - l2ChainIDsStr := strings.Split(strings.TrimSpace(l2ChainIDsRaw), ",") - l2ChainIDs := make([]common.Hash, len(l2ChainIDsStr)) - for i, idStr := range l2ChainIDsStr { - id, err := op_service.Parse256BitChainID(idStr) - if err != nil { - return fmt.Errorf("invalid chain ID: %w", err) - } - l2ChainIDs[i] = id - } - - err := Init(InitConfig{ - L1ChainID: l1ChainID, - Outdir: outdir, - L2ChainIDs: l2ChainIDs, - }) - if err != nil { - return err - } - - fmt.Printf("Successfully initialized op-deployer intent in directory: %s\n", outdir) - return nil - } -} - -func Init(cfg InitConfig) error { - if err := cfg.Check(); err != nil { - return fmt.Errorf("invalid config for init: %w", err) - } - - intent := &state.Intent{ - L1ChainID: cfg.L1ChainID, - FundDevAccounts: true, - ContractsRelease: "op-contracts/v1.6.0", - ContractArtifactsURL: V160ArtifactsURL, - } - - l1ChainIDBig := intent.L1ChainIDBig() - - dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) - if err != nil { - return fmt.Errorf("failed to create dev keys: %w", err) - } - - addrFor := func(key devkeys.Key) common.Address { - // The error below should never happen, so panic if it does. - addr, err := dk.Address(key) - if err != nil { - panic(err) - } - return addr - } - intent.SuperchainRoles = state.SuperchainRoles{ - ProxyAdminOwner: addrFor(devkeys.L1ProxyAdminOwnerRole.Key(l1ChainIDBig)), - ProtocolVersionsOwner: addrFor(devkeys.SuperchainProtocolVersionsOwner.Key(l1ChainIDBig)), - Guardian: addrFor(devkeys.SuperchainConfigGuardianKey.Key(l1ChainIDBig)), - } - - for _, l2ChainID := range cfg.L2ChainIDs { - l2ChainIDBig := l2ChainID.Big() - intent.Chains = append(intent.Chains, &state.ChainIntent{ - ID: l2ChainID, - Roles: state.ChainRoles{ - ProxyAdminOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l2ChainIDBig)), - SystemConfigOwner: addrFor(devkeys.SystemConfigOwner.Key(l2ChainIDBig)), - GovernanceTokenOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l2ChainIDBig)), - UnsafeBlockSigner: addrFor(devkeys.SequencerP2PRole.Key(l2ChainIDBig)), - Batcher: addrFor(devkeys.BatcherRole.Key(l2ChainIDBig)), - Proposer: addrFor(devkeys.ProposerRole.Key(l2ChainIDBig)), - Challenger: addrFor(devkeys.ChallengerRole.Key(l2ChainIDBig)), - }, - }) - } - - st := &state.State{ - Version: 1, - } - - stat, err := os.Stat(cfg.Outdir) - if errors.Is(err, os.ErrNotExist) { - if err := os.MkdirAll(cfg.Outdir, 0755); err != nil { - return fmt.Errorf("failed to create outdir: %w", err) - } - } else if err != nil { - return fmt.Errorf("failed to stat outdir: %w", err) - } else if !stat.IsDir() { - return fmt.Errorf("outdir is not a directory") - } - - if err := intent.WriteToFile(path.Join(cfg.Outdir, "intent.toml")); err != nil { - return fmt.Errorf("failed to write intent to file: %w", err) - } - if err := st.WriteToFile(path.Join(cfg.Outdir, "state.json")); err != nil { - return fmt.Errorf("failed to write state to file: %w", err) - } - return nil -} diff --git a/op-chain-ops/deployer/integration_test/apply_test.go b/op-chain-ops/deployer/integration_test/apply_test.go deleted file mode 100644 index bddefe53d411e..0000000000000 --- a/op-chain-ops/deployer/integration_test/apply_test.go +++ /dev/null @@ -1,384 +0,0 @@ -package integration_test - -import ( - "context" - "fmt" - "log/slog" - "math/big" - "net/url" - "os" - "path" - "runtime" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-service/testutils/anvil" - crypto "github.com/ethereum/go-ethereum/crypto" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" - "github.com/holiman/uint256" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" - opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum-optimism/optimism/op-service/testutils/kurtosisutil" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/stretchr/testify/require" -) - -const TestParams = ` -participants: - - el_type: geth - el_extra_params: - - "--gcmode=archive" - - "--rpc.txfeecap=0" - cl_type: lighthouse -network_params: - prefunded_accounts: '{ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { "balance": "1000000ETH" } }' - additional_preloaded_contracts: '{ - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - balance: "0ETH", - code: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - storage: {}, - nonce: 0, - secretKey: "0x" - } - }' - network_id: "77799777" - seconds_per_slot: 3 - genesis_delay: 0 -` - -type deployerKey struct{} - -func (d *deployerKey) HDPath() string { - return "m/44'/60'/0'/0/0" -} - -func (d *deployerKey) String() string { - return "deployer-key" -} - -func TestEndToEndApply(t *testing.T) { - kurtosisutil.Test(t) - - lgr := testlog.Logger(t, slog.LevelDebug) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - enclaveCtx := kurtosisutil.StartEnclave(t, ctx, lgr, "github.com/ethpandaops/ethereum-package", TestParams) - - service, err := enclaveCtx.GetServiceContext("el-1-geth-lighthouse") - require.NoError(t, err) - - ip := service.GetMaybePublicIPAddress() - ports := service.GetPublicPorts() - rpcURL := fmt.Sprintf("http://%s:%d", ip, ports["rpc"].GetNumber()) - l1Client, err := ethclient.Dial(rpcURL) - require.NoError(t, err) - - depKey := new(deployerKey) - l1ChainID := big.NewInt(77799777) - dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) - require.NoError(t, err) - pk, err := dk.Secret(depKey) - require.NoError(t, err) - signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(pk, l1ChainID)) - - id := uint256.NewInt(1) - - deployerAddr, err := dk.Address(depKey) - require.NoError(t, err) - - env := &pipeline.Env{ - Workdir: t.TempDir(), - L1Client: l1Client, - Signer: signer, - Deployer: deployerAddr, - Logger: lgr, - } - - t.Run("initial chain", func(t *testing.T) { - intent, st := makeIntent(t, l1ChainID, dk, id) - - require.NoError(t, deployer.ApplyPipeline( - ctx, - env, - intent, - st, - )) - - addrs := []struct { - name string - addr common.Address - }{ - {"SuperchainProxyAdmin", st.SuperchainDeployment.ProxyAdminAddress}, - {"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress}, - {"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress}, - {"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress}, - {"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress}, - {"OpcmProxy", st.ImplementationsDeployment.OpcmProxyAddress}, - {"DelayedWETHImpl", st.ImplementationsDeployment.DelayedWETHImplAddress}, - {"OptimismPortalImpl", st.ImplementationsDeployment.OptimismPortalImplAddress}, - {"PreimageOracleSingleton", st.ImplementationsDeployment.PreimageOracleSingletonAddress}, - {"MipsSingleton", st.ImplementationsDeployment.MipsSingletonAddress}, - {"SystemConfigImpl", st.ImplementationsDeployment.SystemConfigImplAddress}, - {"L1CrossDomainMessengerImpl", st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress}, - {"L1ERC721BridgeImpl", st.ImplementationsDeployment.L1ERC721BridgeImplAddress}, - {"L1StandardBridgeImpl", st.ImplementationsDeployment.L1StandardBridgeImplAddress}, - {"OptimismMintableERC20FactoryImpl", st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress}, - {"DisputeGameFactoryImpl", st.ImplementationsDeployment.DisputeGameFactoryImplAddress}, - } - for _, addr := range addrs { - t.Run(addr.name, func(t *testing.T) { - code, err := l1Client.CodeAt(ctx, addr.addr, nil) - require.NoError(t, err) - require.NotEmpty(t, code, "contracts %s at %s has no code", addr.name, addr.addr) - }) - } - - validateOPChainDeployment(t, ctx, l1Client, st) - }) - - t.Run("subsequent chain", func(t *testing.T) { - newID := uint256.NewInt(2) - intent, st := makeIntent(t, l1ChainID, dk, newID) - env.Workdir = t.TempDir() - - require.NoError(t, deployer.ApplyPipeline( - ctx, - env, - intent, - st, - )) - - addrs := []struct { - name string - addr common.Address - }{ - {"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress}, - {"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress}, - {"OpcmProxy", st.ImplementationsDeployment.OpcmProxyAddress}, - } - for _, addr := range addrs { - t.Run(addr.name, func(t *testing.T) { - code, err := l1Client.CodeAt(ctx, addr.addr, nil) - require.NoError(t, err) - require.NotEmpty(t, code, "contracts %s at %s has no code", addr.name, addr.addr) - }) - } - - validateOPChainDeployment(t, ctx, l1Client, st) - }) -} - -func makeIntent( - t *testing.T, - l1ChainID *big.Int, - dk *devkeys.MnemonicDevKeys, - l2ChainID *uint256.Int, -) (*state.Intent, *state.State) { - _, testFilename, _, ok := runtime.Caller(0) - require.Truef(t, ok, "failed to get test filename") - monorepoDir := path.Join(path.Dir(testFilename), "..", "..", "..") - artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts") - artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir)) - require.NoError(t, err) - - addrFor := func(key devkeys.Key) common.Address { - addr, err := dk.Address(key) - require.NoError(t, err) - return addr - } - - intent := &state.Intent{ - L1ChainID: l1ChainID.Uint64(), - SuperchainRoles: state.SuperchainRoles{ - ProxyAdminOwner: addrFor(devkeys.L1ProxyAdminOwnerRole.Key(l1ChainID)), - ProtocolVersionsOwner: addrFor(devkeys.SuperchainDeployerKey.Key(l1ChainID)), - Guardian: addrFor(devkeys.SuperchainConfigGuardianKey.Key(l1ChainID)), - }, - FundDevAccounts: true, - ContractArtifactsURL: (*state.ArtifactsURL)(artifactsURL), - ContractsRelease: "dev", - Chains: []*state.ChainIntent{ - { - ID: l2ChainID.Bytes32(), - Roles: state.ChainRoles{ - ProxyAdminOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)), - SystemConfigOwner: addrFor(devkeys.SystemConfigOwner.Key(l1ChainID)), - GovernanceTokenOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)), - UnsafeBlockSigner: addrFor(devkeys.SequencerP2PRole.Key(l1ChainID)), - Batcher: addrFor(devkeys.BatcherRole.Key(l1ChainID)), - Proposer: addrFor(devkeys.ProposerRole.Key(l1ChainID)), - Challenger: addrFor(devkeys.ChallengerRole.Key(l1ChainID)), - }, - }, - }, - } - st := &state.State{ - Version: 1, - } - return intent, st -} - -func validateOPChainDeployment(t *testing.T, ctx context.Context, l1Client *ethclient.Client, st *state.State) { - for _, chainState := range st.Chains { - chainAddrs := []struct { - name string - addr common.Address - }{ - {"ProxyAdminAddress", chainState.ProxyAdminAddress}, - {"AddressManagerAddress", chainState.AddressManagerAddress}, - {"L1ERC721BridgeProxyAddress", chainState.L1ERC721BridgeProxyAddress}, - {"SystemConfigProxyAddress", chainState.SystemConfigProxyAddress}, - {"OptimismMintableERC20FactoryProxyAddress", chainState.OptimismMintableERC20FactoryProxyAddress}, - {"L1StandardBridgeProxyAddress", chainState.L1StandardBridgeProxyAddress}, - {"L1CrossDomainMessengerProxyAddress", chainState.L1CrossDomainMessengerProxyAddress}, - {"OptimismPortalProxyAddress", chainState.OptimismPortalProxyAddress}, - {"DisputeGameFactoryProxyAddress", chainState.DisputeGameFactoryProxyAddress}, - {"AnchorStateRegistryProxyAddress", chainState.AnchorStateRegistryProxyAddress}, - {"FaultDisputeGameAddress", chainState.FaultDisputeGameAddress}, - {"PermissionedDisputeGameAddress", chainState.PermissionedDisputeGameAddress}, - {"DelayedWETHPermissionedGameProxyAddress", chainState.DelayedWETHPermissionedGameProxyAddress}, - // {"DelayedWETHPermissionlessGameProxyAddress", chainState.DelayedWETHPermissionlessGameProxyAddress}, - } - for _, addr := range chainAddrs { - // TODO Delete this `if`` block once FaultDisputeGameAddress is deployed. - if addr.name == "FaultDisputeGameAddress" { - continue - } - t.Run(addr.name, func(t *testing.T) { - code, err := l1Client.CodeAt(ctx, addr.addr, nil) - require.NoError(t, err) - require.NotEmpty(t, code, "contracts %s at %s for chain %s has no code", addr.name, addr.addr, chainState.ID) - }) - } - - t.Run("l2 genesis", func(t *testing.T) { - require.Greater(t, len(chainState.Allocs), 0) - }) - } -} - -func TestApplyExistingOPCM(t *testing.T) { - anvil.Test(t) - - forkRPCUrl := os.Getenv("SEPOLIA_RPC_URL") - if forkRPCUrl == "" { - t.Skip("no fork RPC URL provided") - } - - lgr := testlog.Logger(t, slog.LevelDebug) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - runner, err := anvil.New( - forkRPCUrl, - lgr, - ) - require.NoError(t, err) - - require.NoError(t, runner.Start(ctx)) - t.Cleanup(func() { - require.NoError(t, runner.Stop()) - }) - - l1Client, err := ethclient.Dial(runner.RPCUrl()) - require.NoError(t, err) - - l1ChainID := big.NewInt(11155111) - dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) - require.NoError(t, err) - // index 0 from Anvil's test set - priv, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") - require.NoError(t, err) - signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(priv, l1ChainID)) - deployerAddr := common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") - - l2ChainID := uint256.NewInt(1) - - env := &pipeline.Env{ - Workdir: t.TempDir(), - L1Client: l1Client, - Signer: signer, - Deployer: deployerAddr, - Logger: lgr, - } - - intent, st := makeIntent(t, l1ChainID, dk, l2ChainID) - intent.ContractsRelease = "op-contracts/v1.6.0" - - require.NoError(t, deployer.ApplyPipeline( - ctx, - env, - intent, - st, - )) - - validateOPChainDeployment(t, ctx, l1Client, st) -} - -func TestL2BlockTimeOverride(t *testing.T) { - kurtosisutil.Test(t) - - lgr := testlog.Logger(t, slog.LevelDebug) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - enclaveCtx := kurtosisutil.StartEnclave(t, ctx, lgr, "github.com/ethpandaops/ethereum-package", TestParams) - - service, err := enclaveCtx.GetServiceContext("el-1-geth-lighthouse") - require.NoError(t, err) - - ip := service.GetMaybePublicIPAddress() - ports := service.GetPublicPorts() - rpcURL := fmt.Sprintf("http://%s:%d", ip, ports["rpc"].GetNumber()) - l1Client, err := ethclient.Dial(rpcURL) - require.NoError(t, err) - - depKey := new(deployerKey) - l1ChainID := big.NewInt(77799777) - dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) - require.NoError(t, err) - pk, err := dk.Secret(depKey) - require.NoError(t, err) - signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(pk, l1ChainID)) - - id := uint256.NewInt(1) - - deployerAddr, err := dk.Address(depKey) - require.NoError(t, err) - - env := &pipeline.Env{ - Workdir: t.TempDir(), - L1Client: l1Client, - Signer: signer, - Deployer: deployerAddr, - Logger: lgr, - } - - intent, st := makeIntent(t, l1ChainID, dk, id) - - intent.GlobalDeployOverrides = map[string]interface{}{ - "l2BlockTime": float64(3), - } - - require.NoError(t, deployer.ApplyPipeline( - ctx, - env, - intent, - st, - )) - - cfg, err := state.CombineDeployConfig(intent, &state.ChainIntent{}, st, st.Chains[0]) - require.NoError(t, err) - - require.Equal(t, uint64(3), cfg.L2InitializationConfig.L2CoreDeployConfig.L2BlockTime, "L2 block time should be 3 seconds") -} diff --git a/op-chain-ops/deployer/opcm/opchain.go b/op-chain-ops/deployer/opcm/opchain.go deleted file mode 100644 index 7a750f72fb0d3..0000000000000 --- a/op-chain-ops/deployer/opcm/opchain.go +++ /dev/null @@ -1,330 +0,0 @@ -package opcm - -import ( - "context" - "fmt" - "math/big" - "strings" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/broadcaster" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - "github.com/ethereum-optimism/optimism/op-chain-ops/script" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/holiman/uint256" -) - -// PermissionedGameStartingAnchorRoots is a root of bytes32(hex"dead") for the permissioned game at block 0, -// and no root for the permissionless game. -var PermissionedGameStartingAnchorRoots = []byte{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -} - -type DeployOPChainInput struct { - OpChainProxyAdminOwner common.Address - SystemConfigOwner common.Address - Batcher common.Address - UnsafeBlockSigner common.Address - Proposer common.Address - Challenger common.Address - - BasefeeScalar uint32 - BlobBaseFeeScalar uint32 - L2ChainId *big.Int - OpcmProxy common.Address - SaltMixer string - GasLimit uint64 - - DisputeGameType uint32 - DisputeAbsolutePrestate common.Hash - DisputeMaxGameDepth uint64 - DisputeSplitDepth uint64 - DisputeClockExtension uint64 - DisputeMaxClockDuration uint64 -} - -func (input *DeployOPChainInput) InputSet() bool { - return true -} - -func (input *DeployOPChainInput) StartingAnchorRoots() []byte { - return PermissionedGameStartingAnchorRoots -} - -type DeployOPChainOutput struct { - OpChainProxyAdmin common.Address - AddressManager common.Address - L1ERC721BridgeProxy common.Address - SystemConfigProxy common.Address - OptimismMintableERC20FactoryProxy common.Address - L1StandardBridgeProxy common.Address - L1CrossDomainMessengerProxy common.Address - // Fault proof contracts below. - OptimismPortalProxy common.Address - DisputeGameFactoryProxy common.Address - AnchorStateRegistryProxy common.Address - AnchorStateRegistryImpl common.Address - FaultDisputeGame common.Address - PermissionedDisputeGame common.Address - DelayedWETHPermissionedGameProxy common.Address - DelayedWETHPermissionlessGameProxy common.Address -} - -func (output *DeployOPChainOutput) CheckOutput(input common.Address) error { - return nil -} - -type DeployOPChainScript struct { - Run func(input, output common.Address) error -} - -func DeployOPChain(host *script.Host, input DeployOPChainInput) (DeployOPChainOutput, error) { - var dco DeployOPChainOutput - inputAddr := host.NewScriptAddress() - outputAddr := host.NewScriptAddress() - - cleanupInput, err := script.WithPrecompileAtAddress[*DeployOPChainInput](host, inputAddr, &input) - if err != nil { - return dco, fmt.Errorf("failed to insert DeployOPChainInput precompile: %w", err) - } - defer cleanupInput() - host.Label(inputAddr, "DeployOPChainInput") - - cleanupOutput, err := script.WithPrecompileAtAddress[*DeployOPChainOutput](host, outputAddr, &dco, - script.WithFieldSetter[*DeployOPChainOutput]) - if err != nil { - return dco, fmt.Errorf("failed to insert DeployOPChainOutput precompile: %w", err) - } - defer cleanupOutput() - host.Label(outputAddr, "DeployOPChainOutput") - - deployScript, cleanupDeploy, err := script.WithScript[DeployOPChainScript](host, "DeployOPChain.s.sol", "DeployOPChain") - if err != nil { - return dco, fmt.Errorf("failed to load DeployOPChain script: %w", err) - } - defer cleanupDeploy() - - if err := deployScript.Run(inputAddr, outputAddr); err != nil { - return dco, fmt.Errorf("failed to run DeployOPChain script: %w", err) - } - - return dco, nil -} - -// opcmRoles is an internal struct used to pass the roles to OPSM. See opcmDeployInput for more info. -type opcmRoles struct { - OpChainProxyAdminOwner common.Address - SystemConfigOwner common.Address - Batcher common.Address - UnsafeBlockSigner common.Address - Proposer common.Address - Challenger common.Address -} - -// opcmDeployInput is the input struct for the deploy method of the OPStackManager contract. We -// define a separate struct here to match what the OPSM contract expects. -type opcmDeployInput struct { - Roles opcmRoles - BasefeeScalar uint32 - BlobBasefeeScalar uint32 - L2ChainId *big.Int - StartingAnchorRoots []byte - SaltMixer string - GasLimit uint64 - DisputeGameType uint32 - DisputeAbsolutePrestate common.Hash - DisputeMaxGameDepth *big.Int - DisputeSplitDepth *big.Int - DisputeClockExtension uint64 - DisputeMaxClockDuration uint64 -} - -// decodeOutputABIJSON defines an ABI for a fake method called "decodeOutput" that returns the -// DeployOutput struct. This allows the code in the deployer to decode directly into a struct -// using Geth's ABI library. -const decodeOutputABIJSON = ` -[ - { - "type": "function", - "name": "decodeOutput", - "inputs": [], - "outputs": [ - { - "name": "output", - "indexed": false, - "type": "tuple", - "components": [ - { - "name": "opChainProxyAdmin", - "type": "address" - }, - { - "name": "addressManager", - "type": "address" - }, - { - "name": "l1ERC721BridgeProxy", - "type": "address" - }, - { - "name": "systemConfigProxy", - "type": "address" - }, - { - "name": "optimismMintableERC20FactoryProxy", - "type": "address" - }, - { - "name": "l1StandardBridgeProxy", - "type": "address" - }, - { - "name": "l1CrossDomainMessengerProxy", - "type": "address" - }, - { - "name": "optimismPortalProxy", - "type": "address" - }, - { - "name": "disputeGameFactoryProxy", - "type": "address" - }, - { - "name": "anchorStateRegistryProxy", - "type": "address" - }, - { - "name": "anchorStateRegistryImpl", - "type": "address" - }, - { - "name": "faultDisputeGame", - "type": "address", - "internalType": "contract FaultDisputeGame" - }, - { - "name": "permissionedDisputeGame", - "type": "address" - }, - { - "name": "delayedWETHPermissionedGameProxy", - "type": "address" - }, - { - "name": "delayedWETHPermissionlessGameProxy", - "type": "address" - } - ] - } - ] - } -] -` - -var decodeOutputABI abi.ABI - -// DeployOPChainRaw deploys an OP Chain using a raw call to a pre-deployed OPSM contract. -func DeployOPChainRaw( - ctx context.Context, - l1 *ethclient.Client, - bcast broadcaster.Broadcaster, - deployer common.Address, - artifacts foundry.StatDirFs, - input DeployOPChainInput, -) (DeployOPChainOutput, error) { - var out DeployOPChainOutput - - artifactsFS := &foundry.ArtifactsFS{FS: artifacts} - opcmArtifacts, err := artifactsFS.ReadArtifact("OPContractsManager.sol", "OPContractsManager") - if err != nil { - return out, fmt.Errorf("failed to read OPStackManager artifact: %w", err) - } - - opcmABI := opcmArtifacts.ABI - calldata, err := opcmABI.Pack("deploy", opcmDeployInput{ - Roles: opcmRoles{ - OpChainProxyAdminOwner: input.OpChainProxyAdminOwner, - SystemConfigOwner: input.SystemConfigOwner, - Batcher: input.Batcher, - UnsafeBlockSigner: input.UnsafeBlockSigner, - Proposer: input.Proposer, - Challenger: input.Challenger, - }, - BasefeeScalar: input.BasefeeScalar, - BlobBasefeeScalar: input.BlobBaseFeeScalar, - L2ChainId: input.L2ChainId, - StartingAnchorRoots: input.StartingAnchorRoots(), - SaltMixer: input.SaltMixer, - GasLimit: input.GasLimit, - DisputeGameType: input.DisputeGameType, - DisputeAbsolutePrestate: input.DisputeAbsolutePrestate, - DisputeMaxGameDepth: new(big.Int).SetUint64(input.DisputeMaxGameDepth), - DisputeSplitDepth: new(big.Int).SetUint64(input.DisputeSplitDepth), - DisputeClockExtension: input.DisputeClockExtension, - DisputeMaxClockDuration: input.DisputeMaxClockDuration, - }) - if err != nil { - return out, fmt.Errorf("failed to pack deploy input: %w", err) - } - - nonce, err := l1.NonceAt(ctx, deployer, nil) - if err != nil { - return out, fmt.Errorf("failed to read nonce: %w", err) - } - - bcast.Hook(script.Broadcast{ - From: deployer, - To: input.OpcmProxy, - Input: calldata, - Value: (*hexutil.U256)(uint256.NewInt(0)), - // use hardcoded 19MM gas for now since this is roughly what we've seen this deployment cost. - GasUsed: 19_000_000, - Type: script.BroadcastCall, - Nonce: nonce, - }) - - results, err := bcast.Broadcast(ctx) - if err != nil { - return out, fmt.Errorf("failed to broadcast OP chain deployment: %w", err) - } - - deployedEvent := opcmABI.Events["Deployed"] - res := results[0] - - for _, log := range res.Receipt.Logs { - if log.Topics[0] != deployedEvent.ID { - continue - } - - type EventData struct { - DeployOutput []byte - } - var data EventData - if err := opcmABI.UnpackIntoInterface(&data, "Deployed", log.Data); err != nil { - return out, fmt.Errorf("failed to unpack Deployed event: %w", err) - } - - type OutputData struct { - Output DeployOPChainOutput - } - var outData OutputData - if err := decodeOutputABI.UnpackIntoInterface(&outData, "decodeOutput", data.DeployOutput); err != nil { - return out, fmt.Errorf("failed to unpack DeployOutput: %w", err) - } - - return outData.Output, nil - } - - return out, fmt.Errorf("failed to find Deployed event") -} - -func init() { - var err error - decodeOutputABI, err = abi.JSON(strings.NewReader(decodeOutputABIJSON)) - if err != nil { - panic(fmt.Sprintf("failed to parse decodeOutput ABI: %v", err)) - } -} diff --git a/op-chain-ops/deployer/opcm/standard-versions-sepolia.toml b/op-chain-ops/deployer/opcm/standard-versions-sepolia.toml deleted file mode 100644 index 277f9d0963067..0000000000000 --- a/op-chain-ops/deployer/opcm/standard-versions-sepolia.toml +++ /dev/null @@ -1,23 +0,0 @@ -[releases] - -# Contracts which are -# * unproxied singletons: specify a standard "address" -# * proxied : specify a standard "implementation_address" -# * neither : specify neither a standard "address" nor "implementation_address" - -# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 -[releases."op-contracts/v1.6.0"] -optimism_portal = { version = "3.10.0", implementation_address = "0x35028bae87d71cbc192d545d38f960ba30b4b233" } -system_config = { version = "2.2.0", implementation_address = "0xCcdd86d581e40fb5a1C77582247BC493b6c8B169" } -anchor_state_registry = { version = "2.0.0" } -delayed_weth = { version = "1.1.0", implementation_address = "0x07f69b19532476c6cd03056d6bc3f1b110ab7538" } -dispute_game_factory = { version = "1.0.0", implementation_address = "0xa51bea7e4d34206c0bcb04a776292f2f19f0beec" } -fault_dispute_game = { version = "1.3.0" } -permissioned_dispute_game = { version = "1.3.0" } -mips = { version = "1.1.0", address = "0x47B0E34C1054009e696BaBAAd56165e1e994144d" } -preimage_oracle = { version = "1.1.2", address = "0x92240135b46fc1142dA181f550aE8f595B858854" } -l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } -l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d" } -l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64b5a5ed26dcb17370ff4d33a8d503f0fbd06cff" } -# l2_output_oracle -- This contract not used in fault proofs -optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xe01efbeb1089d1d1db9c6c8b135c934c0734c846" } diff --git a/op-chain-ops/deployer/opcm/standard.go b/op-chain-ops/deployer/opcm/standard.go deleted file mode 100644 index 5a2a129f199a7..0000000000000 --- a/op-chain-ops/deployer/opcm/standard.go +++ /dev/null @@ -1,106 +0,0 @@ -package opcm - -import ( - "embed" - "fmt" - - "github.com/BurntSushi/toml" - - "github.com/ethereum-optimism/superchain-registry/superchain" - "github.com/ethereum/go-ethereum/common" -) - -//go:embed standard-versions-mainnet.toml -var StandardVersionsMainnetData string - -//go:embed standard-versions-sepolia.toml -var StandardVersionsSepoliaData string - -var StandardVersionsSepolia StandardVersions - -var StandardVersionsMainnet StandardVersions - -type StandardVersions struct { - Releases map[string]StandardVersionsReleases `toml:"releases"` -} - -type StandardVersionsReleases struct { - OptimismPortal StandardVersionRelease `toml:"optimism_portal"` - SystemConfig StandardVersionRelease `toml:"system_config"` - AnchorStateRegistry StandardVersionRelease `toml:"anchor_state_registry"` - DelayedWETH StandardVersionRelease `toml:"delayed_weth"` - DisputeGameFactory StandardVersionRelease `toml:"dispute_game_factory"` - FaultDisputeGame StandardVersionRelease `toml:"fault_dispute_game"` - PermissionedDisputeGame StandardVersionRelease `toml:"permissioned_dispute_game"` - MIPS StandardVersionRelease `toml:"mips"` - PreimageOracle StandardVersionRelease `toml:"preimage_oracle"` - L1CrossDomainMessenger StandardVersionRelease `toml:"l1_cross_domain_messenger"` - L1ERC721Bridge StandardVersionRelease `toml:"l1_erc721_bridge"` - L1StandardBridge StandardVersionRelease `toml:"l1_standard_bridge"` - OptimismMintableERC20Factory StandardVersionRelease `toml:"optimism_mintable_erc20_factory"` -} - -type StandardVersionRelease struct { - Version string `toml:"version"` - ImplementationAddress common.Address `toml:"implementation_address"` - Address common.Address `toml:"address"` -} - -var _ embed.FS - -func StandardVersionsFor(chainID uint64) (string, error) { - switch chainID { - case 1: - return StandardVersionsMainnetData, nil - case 11155111: - return StandardVersionsSepoliaData, nil - default: - return "", fmt.Errorf("unsupported chain ID: %d", chainID) - } -} - -func SuperchainFor(chainID uint64) (*superchain.Superchain, error) { - switch chainID { - case 1: - return superchain.Superchains["mainnet"], nil - case 11155111: - return superchain.Superchains["sepolia"], nil - default: - return nil, fmt.Errorf("unsupported chain ID: %d", chainID) - } -} - -func ManagerImplementationAddrFor(chainID uint64) (common.Address, error) { - switch chainID { - case 11155111: - // Generated using the bootstrap command on 10/02/2024. - return common.HexToAddress("0x0f29118caed0f72873701bcc079398c594b6f8e4"), nil - default: - return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) - } -} - -func ManagerOwnerAddrFor(chainID uint64) (common.Address, error) { - switch chainID { - case 1: - // Set to superchain proxy admin - return common.HexToAddress("0x543bA4AADBAb8f9025686Bd03993043599c6fB04"), nil - case 11155111: - // Set to development multisig - return common.HexToAddress("0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B"), nil - default: - return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) - } -} - -func init() { - StandardVersionsMainnet = StandardVersions{} - if err := toml.Unmarshal([]byte(StandardVersionsMainnetData), &StandardVersionsMainnet); err != nil { - panic(err) - } - - StandardVersionsSepolia = StandardVersions{} - if err := toml.Unmarshal([]byte(StandardVersionsSepoliaData), &StandardVersionsSepolia); err != nil { - panic(err) - } -} diff --git a/op-chain-ops/deployer/pipeline/downloader.go b/op-chain-ops/deployer/pipeline/downloader.go deleted file mode 100644 index 8932792f822ff..0000000000000 --- a/op-chain-ops/deployer/pipeline/downloader.go +++ /dev/null @@ -1,138 +0,0 @@ -package pipeline - -import ( - "archive/tar" - "bufio" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "os" - "path" - "strings" - "time" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" -) - -var ErrUnsupportedArtifactsScheme = errors.New("unsupported artifacts URL scheme") - -type DownloadProgressor func(current, total int64) - -type CleanupFunc func() error - -var noopCleanup = func() error { return nil } - -func DownloadArtifacts(ctx context.Context, artifactsURL *state.ArtifactsURL, progress DownloadProgressor) (foundry.StatDirFs, CleanupFunc, error) { - switch artifactsURL.Scheme { - case "http", "https": - req, err := http.NewRequestWithContext(ctx, http.MethodGet, (*url.URL)(artifactsURL).String(), nil) - if err != nil { - return nil, nil, fmt.Errorf("failed to create request: %w", err) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, nil, fmt.Errorf("failed to download artifacts: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, nil, fmt.Errorf("failed to download artifacts: invalid status code %s", resp.Status) - } - - tmpDir, err := os.MkdirTemp("", "op-deployer-artifacts-*") - if err != nil { - return nil, nil, fmt.Errorf("failed to create temp dir: %w", err) - } - - pr := &progressReader{ - r: resp.Body, - progress: progress, - total: resp.ContentLength, - } - - gr, err := gzip.NewReader(pr) - if err != nil { - return nil, nil, fmt.Errorf("failed to create gzip reader: %w", err) - } - defer gr.Close() - - tr := tar.NewReader(gr) - if err := untar(tmpDir, tr); err != nil { - return nil, nil, fmt.Errorf("failed to untar: %w", err) - } - - fs := os.DirFS(path.Join(tmpDir, "forge-artifacts")) - cleanup := func() error { - return os.RemoveAll(tmpDir) - } - return fs.(foundry.StatDirFs), cleanup, nil - case "file": - fs := os.DirFS(artifactsURL.Path) - return fs.(foundry.StatDirFs), noopCleanup, nil - default: - return nil, nil, ErrUnsupportedArtifactsScheme - } -} - -type progressReader struct { - r io.Reader - progress DownloadProgressor - curr int64 - total int64 - lastPrint time.Time -} - -func (pr *progressReader) Read(p []byte) (int, error) { - - n, err := pr.r.Read(p) - pr.curr += int64(n) - if pr.progress != nil && time.Since(pr.lastPrint) > 1*time.Second { - pr.progress(pr.curr, pr.total) - pr.lastPrint = time.Now() - } - return n, err -} - -func untar(dir string, tr *tar.Reader) error { - for { - hdr, err := tr.Next() - if err == io.EOF { - return nil - } - if err != nil { - return fmt.Errorf("failed to read tar header: %w", err) - } - - cleanedName := path.Clean(hdr.Name) - if strings.Contains(cleanedName, "..") { - return fmt.Errorf("invalid file path: %s", hdr.Name) - } - dst := path.Join(dir, cleanedName) - if hdr.FileInfo().IsDir() { - if err := os.MkdirAll(dst, 0o755); err != nil { - return fmt.Errorf("failed to create directory: %w", err) - } - continue - } - - f, err := os.Create(dst) - buf := bufio.NewWriter(f) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - if _, err := io.Copy(buf, tr); err != nil { - _ = f.Close() - return fmt.Errorf("failed to write file: %w", err) - } - if err := buf.Flush(); err != nil { - return fmt.Errorf("failed to flush buffer: %w", err) - } - _ = f.Close() - } -} diff --git a/op-chain-ops/deployer/pipeline/downloader_test.go b/op-chain-ops/deployer/pipeline/downloader_test.go deleted file mode 100644 index 277409461a41e..0000000000000 --- a/op-chain-ops/deployer/pipeline/downloader_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package pipeline - -import ( - "context" - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - "testing" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/stretchr/testify/require" -) - -func TestDownloadArtifacts(t *testing.T) { - f, err := os.OpenFile("testdata/artifacts.tar.gz", os.O_RDONLY, 0o644) - require.NoError(t, err) - defer f.Close() - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, err := io.Copy(w, f) - require.NoError(t, err) - })) - defer ts.Close() - - ctx := context.Background() - artifactsURL, err := url.Parse(ts.URL) - require.NoError(t, err) - - fs, cleanup, err := DownloadArtifacts(ctx, (*state.ArtifactsURL)(artifactsURL), nil) - require.NoError(t, err) - require.NotNil(t, fs) - defer func() { - require.NoError(t, cleanup()) - }() - - info, err := fs.Stat("WETH98.sol/WETH98.json") - require.NoError(t, err) - require.Greater(t, info.Size(), int64(0)) -} diff --git a/op-chain-ops/deployer/pipeline/env.go b/op-chain-ops/deployer/pipeline/env.go deleted file mode 100644 index d0778122d27a1..0000000000000 --- a/op-chain-ops/deployer/pipeline/env.go +++ /dev/null @@ -1,49 +0,0 @@ -package pipeline - -import ( - "context" - "fmt" - "path" - - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" - "github.com/ethereum-optimism/optimism/op-service/jsonutil" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" -) - -type Env struct { - Workdir string - L1Client *ethclient.Client - Signer opcrypto.SignerFn - Deployer common.Address - Logger log.Logger -} - -func (e *Env) ReadIntent() (*state.Intent, error) { - intentPath := path.Join(e.Workdir, "intent.toml") - intent, err := jsonutil.LoadTOML[state.Intent](intentPath) - if err != nil { - return nil, fmt.Errorf("failed to read intent file: %w", err) - } - return intent, nil -} - -func (e *Env) ReadState() (*state.State, error) { - statePath := path.Join(e.Workdir, "state.json") - st, err := jsonutil.LoadJSON[state.State](statePath) - if err != nil { - return nil, fmt.Errorf("failed to read state file: %w", err) - } - return st, nil -} - -func (e *Env) WriteState(st *state.State) error { - statePath := path.Join(e.Workdir, "state.json") - return st.WriteToFile(statePath) -} - -type Stage func(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, state2 *state.State) error diff --git a/op-chain-ops/deployer/pipeline/host.go b/op-chain-ops/deployer/pipeline/host.go deleted file mode 100644 index 71bc9e29e05c3..0000000000000 --- a/op-chain-ops/deployer/pipeline/host.go +++ /dev/null @@ -1,87 +0,0 @@ -package pipeline - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/broadcaster" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - "github.com/ethereum-optimism/optimism/op-chain-ops/script" - opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" -) - -type BroadcasterFactory func(opts CallScriptBroadcastOpts) (broadcaster.Broadcaster, error) - -func KeyedBroadcaster(opts CallScriptBroadcastOpts) (broadcaster.Broadcaster, error) { - return broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ - Logger: opts.Logger, - ChainID: opts.L1ChainID, - Client: opts.Client, - Signer: opts.Signer, - From: opts.Deployer, - }) -} - -func DiscardBroadcaster(opts CallScriptBroadcastOpts) (broadcaster.Broadcaster, error) { - return broadcaster.DiscardBroadcaster(), nil -} - -type CallScriptBroadcastOpts struct { - L1ChainID *big.Int - Logger log.Logger - ArtifactsFS foundry.StatDirFs - Deployer common.Address - Signer opcrypto.SignerFn - Client *ethclient.Client - Handler func(host *script.Host) error - Broadcaster BroadcasterFactory -} - -func CallScriptBroadcast( - ctx context.Context, - opts CallScriptBroadcastOpts, -) error { - bcaster, err := opts.Broadcaster(opts) - if err != nil { - return fmt.Errorf("failed to create broadcaster: %w", err) - } - - scriptCtx := script.DefaultContext - scriptCtx.Sender = opts.Deployer - scriptCtx.Origin = opts.Deployer - artifacts := &foundry.ArtifactsFS{FS: opts.ArtifactsFS} - h := script.NewHost( - opts.Logger, - artifacts, - nil, - scriptCtx, - script.WithBroadcastHook(bcaster.Hook), - script.WithIsolatedBroadcasts(), - script.WithCreate2Deployer(), - ) - - if err := h.EnableCheats(); err != nil { - return fmt.Errorf("failed to enable cheats: %w", err) - } - - nonce, err := opts.Client.NonceAt(ctx, opts.Deployer, nil) - if err != nil { - return fmt.Errorf("failed to fetch nonce: %w", err) - } - h.SetNonce(opts.Deployer, nonce) - - err = opts.Handler(h) - if err != nil { - return fmt.Errorf("failed to run handler: %w", err) - } - - if _, err := bcaster.Broadcast(ctx); err != nil { - return fmt.Errorf("failed to broadcast: %w", err) - } - - return nil -} diff --git a/op-chain-ops/deployer/pipeline/implementations.go b/op-chain-ops/deployer/pipeline/implementations.go deleted file mode 100644 index 8a743eeadf535..0000000000000 --- a/op-chain-ops/deployer/pipeline/implementations.go +++ /dev/null @@ -1,105 +0,0 @@ -package pipeline - -import ( - "context" - "fmt" - "math/big" - "strings" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - "github.com/ethereum-optimism/optimism/op-chain-ops/script" -) - -func DeployImplementations(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { - lgr := env.Logger.New("stage", "deploy-implementations") - - if !shouldDeployImplementations(intent, st) { - lgr.Info("implementations deployment not needed") - return nil - } - - lgr.Info("deploying implementations") - - var standardVersionsTOML string - var err error - if strings.HasPrefix(intent.ContractsRelease, "op-contracts") { - standardVersionsTOML, err = opcm.StandardVersionsFor(intent.L1ChainID) - if err != nil { - return fmt.Errorf("error getting standard versions TOML: %w", err) - } - } - - var dump *foundry.ForgeAllocs - var dio opcm.DeployImplementationsOutput - err = CallScriptBroadcast( - ctx, - CallScriptBroadcastOpts{ - L1ChainID: big.NewInt(int64(intent.L1ChainID)), - Logger: lgr, - ArtifactsFS: artifactsFS, - Deployer: env.Deployer, - Signer: env.Signer, - Client: env.L1Client, - Broadcaster: KeyedBroadcaster, - Handler: func(host *script.Host) error { - host.ImportState(st.SuperchainDeployment.StateDump) - - dio, err = opcm.DeployImplementations( - host, - opcm.DeployImplementationsInput{ - Salt: st.Create2Salt, - WithdrawalDelaySeconds: big.NewInt(604800), - MinProposalSizeBytes: big.NewInt(126000), - ChallengePeriodSeconds: big.NewInt(86400), - ProofMaturityDelaySeconds: big.NewInt(604800), - DisputeGameFinalityDelaySeconds: big.NewInt(302400), - MipsVersion: big.NewInt(1), - Release: intent.ContractsRelease, - SuperchainConfigProxy: st.SuperchainDeployment.SuperchainConfigProxyAddress, - ProtocolVersionsProxy: st.SuperchainDeployment.ProtocolVersionsProxyAddress, - OpcmProxyOwner: st.SuperchainDeployment.ProxyAdminAddress, - StandardVersionsToml: standardVersionsTOML, - UseInterop: false, - }, - ) - if err != nil { - return fmt.Errorf("error deploying implementations: %w", err) - } - dump, err = host.StateDump() - if err != nil { - return fmt.Errorf("error dumping state: %w", err) - } - return nil - }, - }, - ) - if err != nil { - return fmt.Errorf("error deploying implementations: %w", err) - } - - st.ImplementationsDeployment = &state.ImplementationsDeployment{ - OpcmProxyAddress: dio.OpcmProxy, - DelayedWETHImplAddress: dio.DelayedWETHImpl, - OptimismPortalImplAddress: dio.OptimismPortalImpl, - PreimageOracleSingletonAddress: dio.PreimageOracleSingleton, - MipsSingletonAddress: dio.MipsSingleton, - SystemConfigImplAddress: dio.SystemConfigImpl, - L1CrossDomainMessengerImplAddress: dio.L1CrossDomainMessengerImpl, - L1ERC721BridgeImplAddress: dio.L1ERC721BridgeImpl, - L1StandardBridgeImplAddress: dio.L1StandardBridgeImpl, - OptimismMintableERC20FactoryImplAddress: dio.OptimismMintableERC20FactoryImpl, - DisputeGameFactoryImplAddress: dio.DisputeGameFactoryImpl, - StateDump: dump, - } - if err := env.WriteState(st); err != nil { - return err - } - - return nil -} - -func shouldDeployImplementations(intent *state.Intent, st *state.State) bool { - return st.ImplementationsDeployment == nil -} diff --git a/op-chain-ops/deployer/pipeline/l2genesis.go b/op-chain-ops/deployer/pipeline/l2genesis.go deleted file mode 100644 index 25aa316c78a50..0000000000000 --- a/op-chain-ops/deployer/pipeline/l2genesis.go +++ /dev/null @@ -1,97 +0,0 @@ -package pipeline - -import ( - "bytes" - "compress/gzip" - "context" - "encoding/json" - "fmt" - "math/big" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - "github.com/ethereum-optimism/optimism/op-chain-ops/script" - "github.com/ethereum/go-ethereum/common" -) - -func GenerateL2Genesis(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State, chainID common.Hash) error { - lgr := env.Logger.New("stage", "generate-l2-genesis") - - lgr.Info("generating L2 genesis", "id", chainID.Hex()) - - thisIntent, err := intent.Chain(chainID) - if err != nil { - return fmt.Errorf("failed to get chain intent: %w", err) - } - - thisChainState, err := st.Chain(chainID) - if err != nil { - return fmt.Errorf("failed to get chain state: %w", err) - } - - initCfg, err := state.CombineDeployConfig(intent, thisIntent, st, thisChainState) - if err != nil { - return fmt.Errorf("failed to combine L2 init config: %w", err) - } - - var dump *foundry.ForgeAllocs - err = CallScriptBroadcast( - ctx, - CallScriptBroadcastOpts{ - L1ChainID: big.NewInt(int64(intent.L1ChainID)), - Logger: lgr, - ArtifactsFS: artifactsFS, - Deployer: env.Deployer, - Signer: env.Signer, - Client: env.L1Client, - Broadcaster: DiscardBroadcaster, - Handler: func(host *script.Host) error { - err := opcm.L2Genesis(host, &opcm.L2GenesisInput{ - L1Deployments: opcm.L1Deployments{ - L1CrossDomainMessengerProxy: thisChainState.L1CrossDomainMessengerProxyAddress, - L1StandardBridgeProxy: thisChainState.L1StandardBridgeProxyAddress, - L1ERC721BridgeProxy: thisChainState.L1ERC721BridgeProxyAddress, - }, - L2Config: initCfg.L2InitializationConfig, - }) - if err != nil { - return fmt.Errorf("failed to call L2Genesis script: %w", err) - } - - host.Wipe(env.Deployer) - - dump, err = host.StateDump() - if err != nil { - return fmt.Errorf("failed to dump state: %w", err) - } - - return nil - }, - }, - ) - if err != nil { - return fmt.Errorf("failed to call L2Genesis script: %w", err) - } - - var buf bytes.Buffer - gw := gzip.NewWriter(&buf) - if err := json.NewEncoder(gw).Encode(dump); err != nil { - return fmt.Errorf("failed to encode state dump: %w", err) - } - if err := gw.Close(); err != nil { - return fmt.Errorf("failed to close gzip writer: %w", err) - } - thisChainState.Allocs = buf.Bytes() - startHeader, err := env.L1Client.HeaderByNumber(ctx, nil) - if err != nil { - return fmt.Errorf("failed to get start block: %w", err) - } - thisChainState.StartBlock = startHeader - - if err := env.WriteState(st); err != nil { - return fmt.Errorf("failed to write state: %w", err) - } - - return nil -} diff --git a/op-chain-ops/deployer/pipeline/opchain.go b/op-chain-ops/deployer/pipeline/opchain.go deleted file mode 100644 index c97f162e94017..0000000000000 --- a/op-chain-ops/deployer/pipeline/opchain.go +++ /dev/null @@ -1,109 +0,0 @@ -package pipeline - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/broadcaster" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - "github.com/ethereum/go-ethereum/common" -) - -func DeployOPChain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State, chainID common.Hash) error { - lgr := env.Logger.New("stage", "deploy-opchain") - - if !shouldDeployOPChain(intent, st, chainID) { - lgr.Info("opchain deployment not needed") - return nil - } - - lgr.Info("deploying OP chain", "id", chainID.Hex()) - - thisIntent, err := intent.Chain(chainID) - if err != nil { - return fmt.Errorf("failed to get chain intent: %w", err) - } - - input := opcm.DeployOPChainInput{ - OpChainProxyAdminOwner: thisIntent.Roles.ProxyAdminOwner, - SystemConfigOwner: thisIntent.Roles.SystemConfigOwner, - Batcher: thisIntent.Roles.Batcher, - UnsafeBlockSigner: thisIntent.Roles.UnsafeBlockSigner, - Proposer: thisIntent.Roles.Proposer, - Challenger: thisIntent.Roles.Challenger, - BasefeeScalar: 1368, - BlobBaseFeeScalar: 801949, - L2ChainId: chainID.Big(), - OpcmProxy: st.ImplementationsDeployment.OpcmProxyAddress, - SaltMixer: st.Create2Salt.String(), // passing through salt generated at state initialization - GasLimit: 30_000_000, - DisputeGameType: 1, // PERMISSIONED_CANNON Game Type - DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), - DisputeMaxGameDepth: 73, - DisputeSplitDepth: 30, - DisputeClockExtension: 10800, // 3 hours (input in seconds) - DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) - } - - var dco opcm.DeployOPChainOutput - lgr.Info("deploying using existing OPCM", "address", st.ImplementationsDeployment.OpcmProxyAddress.Hex()) - bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ - Logger: lgr, - ChainID: big.NewInt(int64(intent.L1ChainID)), - Client: env.L1Client, - Signer: env.Signer, - From: env.Deployer, - }) - if err != nil { - return fmt.Errorf("failed to create broadcaster: %w", err) - } - dco, err = opcm.DeployOPChainRaw( - ctx, - env.L1Client, - bcaster, - env.Deployer, - artifactsFS, - input, - ) - if err != nil { - return fmt.Errorf("error deploying OP chain: %w", err) - } - - st.Chains = append(st.Chains, &state.ChainState{ - ID: chainID, - ProxyAdminAddress: dco.OpChainProxyAdmin, - AddressManagerAddress: dco.AddressManager, - L1ERC721BridgeProxyAddress: dco.L1ERC721BridgeProxy, - SystemConfigProxyAddress: dco.SystemConfigProxy, - OptimismMintableERC20FactoryProxyAddress: dco.OptimismMintableERC20FactoryProxy, - L1StandardBridgeProxyAddress: dco.L1StandardBridgeProxy, - L1CrossDomainMessengerProxyAddress: dco.L1CrossDomainMessengerProxy, - OptimismPortalProxyAddress: dco.OptimismPortalProxy, - DisputeGameFactoryProxyAddress: dco.DisputeGameFactoryProxy, - AnchorStateRegistryProxyAddress: dco.AnchorStateRegistryProxy, - AnchorStateRegistryImplAddress: dco.AnchorStateRegistryImpl, - FaultDisputeGameAddress: dco.FaultDisputeGame, - PermissionedDisputeGameAddress: dco.PermissionedDisputeGame, - DelayedWETHPermissionedGameProxyAddress: dco.DelayedWETHPermissionedGameProxy, - DelayedWETHPermissionlessGameProxyAddress: dco.DelayedWETHPermissionlessGameProxy, - }) - if err := env.WriteState(st); err != nil { - return err - } - - return nil -} - -func shouldDeployOPChain(intent *state.Intent, st *state.State, chainID common.Hash) bool { - for _, chain := range st.Chains { - if chain.ID == chainID { - return false - } - } - - return true -} diff --git a/op-chain-ops/deployer/pipeline/superchain.go b/op-chain-ops/deployer/pipeline/superchain.go deleted file mode 100644 index 13737475c916b..0000000000000 --- a/op-chain-ops/deployer/pipeline/superchain.go +++ /dev/null @@ -1,83 +0,0 @@ -package pipeline - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum-optimism/optimism/op-chain-ops/script" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" - "github.com/ethereum-optimism/optimism/op-node/rollup" -) - -func DeploySuperchain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { - lgr := env.Logger.New("stage", "deploy-superchain") - - if !shouldDeploySuperchain(intent, st) { - lgr.Info("superchain deployment not needed") - return nil - } - - lgr.Info("deploying superchain") - - var dump *foundry.ForgeAllocs - var dso opcm.DeploySuperchainOutput - var err error - err = CallScriptBroadcast( - ctx, - CallScriptBroadcastOpts{ - L1ChainID: big.NewInt(int64(intent.L1ChainID)), - Logger: lgr, - ArtifactsFS: artifactsFS, - Deployer: env.Deployer, - Signer: env.Signer, - Client: env.L1Client, - Broadcaster: KeyedBroadcaster, - Handler: func(host *script.Host) error { - dso, err = opcm.DeploySuperchain( - host, - opcm.DeploySuperchainInput{ - SuperchainProxyAdminOwner: intent.SuperchainRoles.ProxyAdminOwner, - ProtocolVersionsOwner: intent.SuperchainRoles.ProtocolVersionsOwner, - Guardian: intent.SuperchainRoles.Guardian, - Paused: false, - RequiredProtocolVersion: rollup.OPStackSupport, - RecommendedProtocolVersion: rollup.OPStackSupport, - }, - ) - if err != nil { - return fmt.Errorf("failed to deploy superchain: %w", err) - } - dump, err = host.StateDump() - if err != nil { - return fmt.Errorf("error dumping state: %w", err) - } - return nil - }, - }, - ) - if err != nil { - return fmt.Errorf("error deploying superchain: %w", err) - } - - st.SuperchainDeployment = &state.SuperchainDeployment{ - ProxyAdminAddress: dso.SuperchainProxyAdmin, - SuperchainConfigProxyAddress: dso.SuperchainConfigProxy, - SuperchainConfigImplAddress: dso.SuperchainConfigImpl, - ProtocolVersionsProxyAddress: dso.ProtocolVersionsProxy, - ProtocolVersionsImplAddress: dso.ProtocolVersionsImpl, - StateDump: dump, - } - if err := env.WriteState(st); err != nil { - return err - } - - return nil -} - -func shouldDeploySuperchain(intent *state.Intent, st *state.State) bool { - return st.SuperchainDeployment == nil -} diff --git a/op-chain-ops/deployer/pipeline/testdata/artifacts.tar.gz b/op-chain-ops/deployer/pipeline/testdata/artifacts.tar.gz deleted file mode 100644 index 58f034254f46a..0000000000000 Binary files a/op-chain-ops/deployer/pipeline/testdata/artifacts.tar.gz and /dev/null differ diff --git a/op-chain-ops/deployer/state/artifacts_url.go b/op-chain-ops/deployer/state/artifacts_url.go deleted file mode 100644 index 55910c9f0112e..0000000000000 --- a/op-chain-ops/deployer/state/artifacts_url.go +++ /dev/null @@ -1,34 +0,0 @@ -package state - -import "net/url" - -type ArtifactsURL url.URL - -func (a *ArtifactsURL) MarshalText() ([]byte, error) { - return []byte((*url.URL)(a).String()), nil -} - -func (a *ArtifactsURL) UnmarshalText(text []byte) error { - u, err := url.Parse(string(text)) - if err != nil { - return err - } - *a = ArtifactsURL(*u) - return nil -} - -func ParseArtifactsURL(in string) (*ArtifactsURL, error) { - u, err := url.Parse(in) - if err != nil { - return nil, err - } - return (*ArtifactsURL)(u), nil -} - -func MustParseArtifactsURL(in string) *ArtifactsURL { - u, err := ParseArtifactsURL(in) - if err != nil { - panic(err) - } - return u -} diff --git a/op-chain-ops/deployer/state/deploy_config.go b/op-chain-ops/deployer/state/deploy_config.go deleted file mode 100644 index 5ea8590f537fd..0000000000000 --- a/op-chain-ops/deployer/state/deploy_config.go +++ /dev/null @@ -1,165 +0,0 @@ -package state - -import ( - "encoding/json" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var ( - l2GenesisBlockBaseFeePerGas = hexutil.Big(*(big.NewInt(1000000000))) - - vaultMinWithdrawalAmount = mustHexBigFromHex("0x8ac7230489e80000") -) - -func DefaultDeployConfig() genesis.DeployConfig { - return genesis.DeployConfig{ - L2InitializationConfig: genesis.L2InitializationConfig{ - L2GenesisBlockDeployConfig: genesis.L2GenesisBlockDeployConfig{ - L2GenesisBlockGasLimit: 30_000_000, - L2GenesisBlockBaseFeePerGas: &l2GenesisBlockBaseFeePerGas, - }, - L2VaultsDeployConfig: genesis.L2VaultsDeployConfig{ - BaseFeeVaultWithdrawalNetwork: "local", - L1FeeVaultWithdrawalNetwork: "local", - SequencerFeeVaultWithdrawalNetwork: "local", - SequencerFeeVaultMinimumWithdrawalAmount: vaultMinWithdrawalAmount, - BaseFeeVaultMinimumWithdrawalAmount: vaultMinWithdrawalAmount, - L1FeeVaultMinimumWithdrawalAmount: vaultMinWithdrawalAmount, - }, - GovernanceDeployConfig: genesis.GovernanceDeployConfig{ - EnableGovernance: true, - GovernanceTokenSymbol: "OP", - GovernanceTokenName: "Optimism", - }, - GasPriceOracleDeployConfig: genesis.GasPriceOracleDeployConfig{ - GasPriceOracleBaseFeeScalar: 1368, - GasPriceOracleBlobBaseFeeScalar: 810949, - }, - EIP1559DeployConfig: genesis.EIP1559DeployConfig{ - EIP1559Denominator: 50, - EIP1559DenominatorCanyon: 250, - EIP1559Elasticity: 6, - }, - UpgradeScheduleDeployConfig: genesis.UpgradeScheduleDeployConfig{ - L2GenesisRegolithTimeOffset: u64UtilPtr(0), - L2GenesisCanyonTimeOffset: u64UtilPtr(0), - L2GenesisDeltaTimeOffset: u64UtilPtr(0), - L2GenesisEcotoneTimeOffset: u64UtilPtr(0), - L2GenesisFjordTimeOffset: u64UtilPtr(0), - L2GenesisGraniteTimeOffset: u64UtilPtr(0), - UseInterop: false, - }, - L2CoreDeployConfig: genesis.L2CoreDeployConfig{ - L2BlockTime: 2, - FinalizationPeriodSeconds: 12, - MaxSequencerDrift: 600, - SequencerWindowSize: 3600, - ChannelTimeoutBedrock: 300, - SystemConfigStartBlock: 0, - }, - }, - FaultProofDeployConfig: genesis.FaultProofDeployConfig{ - FaultGameWithdrawalDelay: 604800, - PreimageOracleMinProposalSize: 126000, - PreimageOracleChallengePeriod: 86400, - ProofMaturityDelaySeconds: 604800, - DisputeGameFinalityDelaySeconds: 302400, - }, - } -} - -func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State, chainState *ChainState) (genesis.DeployConfig, error) { - cfg := DefaultDeployConfig() - - var err error - if len(intent.GlobalDeployOverrides) > 0 { - cfg, err = mergeJSON(cfg, intent.GlobalDeployOverrides) - if err != nil { - return genesis.DeployConfig{}, fmt.Errorf("error merging global L2 overrides: %w", err) - - } - } - - if len(chainIntent.DeployOverrides) > 0 { - cfg, err = mergeJSON(cfg, chainIntent.DeployOverrides) - if err != nil { - return genesis.DeployConfig{}, fmt.Errorf("error merging chain L2 overrides: %w", err) - } - } - - cfg.L2ChainID = chainState.ID.Big().Uint64() - cfg.L1DependenciesConfig = genesis.L1DependenciesConfig{ - L1StandardBridgeProxy: chainState.L1StandardBridgeProxyAddress, - L1CrossDomainMessengerProxy: chainState.L1CrossDomainMessengerProxyAddress, - L1ERC721BridgeProxy: chainState.L1ERC721BridgeProxyAddress, - SystemConfigProxy: chainState.SystemConfigProxyAddress, - OptimismPortalProxy: chainState.OptimismPortalProxyAddress, - ProtocolVersionsProxy: state.SuperchainDeployment.ProtocolVersionsProxyAddress, - } - cfg.OperatorDeployConfig = genesis.OperatorDeployConfig{ - BatchSenderAddress: chainIntent.Roles.Batcher, - P2PSequencerAddress: chainIntent.Roles.UnsafeBlockSigner, - } - cfg.BatchInboxAddress = calculateBatchInboxAddr(chainState.ID) - cfg.L1ChainID = intent.L1ChainID - - return cfg, nil -} - -// mergeJSON merges the provided overrides into the input struct. Fields -// must be JSON-serializable for this to work. Overrides are applied in -// order of precedence - i.e., the last overrides will override keys from -// all preceding overrides. -func mergeJSON[T any](in T, overrides ...map[string]any) (T, error) { - var out T - inJSON, err := json.Marshal(in) - if err != nil { - return out, err - } - - var tmpMap map[string]interface{} - if err := json.Unmarshal(inJSON, &tmpMap); err != nil { - return out, err - } - - for _, override := range overrides { - for k, v := range override { - tmpMap[k] = v - } - } - - inJSON, err = json.Marshal(tmpMap) - if err != nil { - return out, err - } - - if err := json.Unmarshal(inJSON, &out); err != nil { - return out, err - } - - return out, nil -} - -func mustHexBigFromHex(hex string) *hexutil.Big { - num := hexutil.MustDecodeBig(hex) - hexBig := hexutil.Big(*num) - return &hexBig -} - -func u64UtilPtr(in uint64) *hexutil.Uint64 { - util := hexutil.Uint64(in) - return &util -} - -func calculateBatchInboxAddr(chainID common.Hash) common.Address { - var out common.Address - copy(out[1:], crypto.Keccak256(chainID[:])[:19]) - return out -} diff --git a/op-chain-ops/deployer/state/intent.go b/op-chain-ops/deployer/state/intent.go deleted file mode 100644 index b365711b0debd..0000000000000 --- a/op-chain-ops/deployer/state/intent.go +++ /dev/null @@ -1,148 +0,0 @@ -package state - -import ( - "fmt" - "math/big" - "strings" - - "github.com/ethereum-optimism/optimism/op-service/ioutil" - "github.com/ethereum-optimism/optimism/op-service/jsonutil" - "github.com/ethereum/go-ethereum/common" -) - -var emptyAddress common.Address - -type Intent struct { - L1ChainID uint64 `json:"l1ChainID" toml:"l1ChainID"` - - SuperchainRoles SuperchainRoles `json:"superchainRoles" toml:"-"` - - FundDevAccounts bool `json:"fundDevAccounts" toml:"fundDevAccounts"` - - ContractArtifactsURL *ArtifactsURL `json:"contractArtifactsURL" toml:"contractArtifactsURL"` - - ContractsRelease string `json:"contractsRelease" toml:"contractsRelease"` - - Chains []*ChainIntent `json:"chains" toml:"chains"` - - GlobalDeployOverrides map[string]any `json:"globalDeployOverrides" toml:"globalDeployOverrides"` -} - -func (c *Intent) L1ChainIDBig() *big.Int { - return big.NewInt(int64(c.L1ChainID)) -} - -func (c *Intent) Check() error { - if c.L1ChainID == 0 { - return fmt.Errorf("l1ChainID must be set") - } - - if c.ContractsRelease == "dev" { - return c.checkDev() - } - - return c.checkProd() -} - -func (c *Intent) Chain(id common.Hash) (*ChainIntent, error) { - for i := range c.Chains { - if c.Chains[i].ID == id { - return c.Chains[i], nil - } - } - - return nil, fmt.Errorf("chain %d not found", id) -} - -func (c *Intent) WriteToFile(path string) error { - return jsonutil.WriteTOML(c, ioutil.ToAtomicFile(path, 0o755)) -} - -func (c *Intent) checkDev() error { - if c.SuperchainRoles.ProxyAdminOwner == emptyAddress { - return fmt.Errorf("proxyAdminOwner must be set") - } - - if c.SuperchainRoles.ProtocolVersionsOwner == emptyAddress { - c.SuperchainRoles.ProtocolVersionsOwner = c.SuperchainRoles.ProxyAdminOwner - } - - if c.SuperchainRoles.Guardian == emptyAddress { - c.SuperchainRoles.Guardian = c.SuperchainRoles.ProxyAdminOwner - } - - if c.ContractArtifactsURL == nil { - return fmt.Errorf("contractArtifactsURL must be set in dev mode") - } - - return nil -} - -func (c *Intent) checkProd() error { - if !strings.HasPrefix(c.ContractsRelease, "op-contracts/") { - return fmt.Errorf("contractsVersion must be either the literal \"dev\" or start with \"op-contracts/\"") - } - - return nil -} - -type SuperchainRoles struct { - ProxyAdminOwner common.Address `json:"proxyAdminOwner" toml:"proxyAdminOwner"` - - ProtocolVersionsOwner common.Address `json:"protocolVersionsOwner" toml:"protocolVersionsOwner"` - - Guardian common.Address `json:"guardian" toml:"guardian"` -} - -type ChainIntent struct { - ID common.Hash `json:"id" toml:"id"` - - Roles ChainRoles `json:"roles" toml:"roles"` - - DeployOverrides map[string]any `json:"deployOverrides" toml:"deployOverrides"` -} - -type ChainRoles struct { - ProxyAdminOwner common.Address `json:"proxyAdminOwner" toml:"proxyAdminOwner"` - - SystemConfigOwner common.Address `json:"systemConfigOwner" toml:"systemConfigOwner"` - - GovernanceTokenOwner common.Address `json:"governanceTokenOwner" toml:"governanceTokenOwner"` - - UnsafeBlockSigner common.Address `json:"unsafeBlockSigner" toml:"unsafeBlockSigner"` - - Batcher common.Address `json:"batcher" toml:"batcher"` - - Proposer common.Address `json:"proposer" toml:"proposer"` - - Challenger common.Address `json:"challenger" toml:"challenger"` -} - -func (c *ChainIntent) Check() error { - var emptyHash common.Hash - if c.ID == emptyHash { - return fmt.Errorf("id must be set") - } - - if c.Roles.ProxyAdminOwner == emptyAddress { - return fmt.Errorf("proxyAdminOwner must be set") - } - - if c.Roles.SystemConfigOwner == emptyAddress { - c.Roles.SystemConfigOwner = c.Roles.ProxyAdminOwner - } - - if c.Roles.GovernanceTokenOwner == emptyAddress { - c.Roles.GovernanceTokenOwner = c.Roles.ProxyAdminOwner - } - - if c.Roles.UnsafeBlockSigner == emptyAddress { - return fmt.Errorf("unsafeBlockSigner must be set") - } - - if c.Roles.Batcher == emptyAddress { - return fmt.Errorf("batcher must be set") - } - - return nil -} diff --git a/op-chain-ops/devkeys/devkeys.go b/op-chain-ops/devkeys/devkeys.go index d0526c8c1d3a4..fdb151e4e6fcf 100644 --- a/op-chain-ops/devkeys/devkeys.go +++ b/op-chain-ops/devkeys/devkeys.go @@ -93,6 +93,21 @@ func (role SuperchainOperatorRole) Key(chainID *big.Int) Key { } } +func (role *SuperchainOperatorRole) UnmarshalText(data []byte) error { + v := string(data) + for i := SuperchainOperatorRole(0); i < 20; i++ { + if i.String() == v { + *role = i + return nil + } + } + return fmt.Errorf("unknown superchain operator role %q", v) +} + +func (role *SuperchainOperatorRole) MarshalText() ([]byte, error) { + return []byte(role.String()), nil +} + // SuperchainOperatorKey is an account specific to an OperationRole of a given OP-Stack chain. type SuperchainOperatorKey struct { ChainID *big.Int @@ -181,6 +196,21 @@ func (role ChainOperatorRole) Key(chainID *big.Int) Key { } } +func (role *ChainOperatorRole) UnmarshalText(data []byte) error { + v := string(data) + for i := ChainOperatorRole(0); i < 20; i++ { + if i.String() == v { + *role = i + return nil + } + } + return fmt.Errorf("unknown chain operator role %q", v) +} + +func (role *ChainOperatorRole) MarshalText() ([]byte, error) { + return []byte(role.String()), nil +} + // ChainOperatorKey is an account specific to an OperationRole of a given OP-Stack chain. type ChainOperatorKey struct { ChainID *big.Int diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 654a042ba445f..a2145d3acdc95 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -94,8 +94,8 @@ type L2GenesisBlockDeployConfig struct { L2GenesisBlockGasUsed hexutil.Uint64 `json:"l2GenesisBlockGasUsed"` L2GenesisBlockParentHash common.Hash `json:"l2GenesisBlockParentHash"` L2GenesisBlockBaseFeePerGas *hexutil.Big `json:"l2GenesisBlockBaseFeePerGas"` - // L2GenesisBlockExtraData is configurable extradata. Will default to []byte("BEDROCK") if left unspecified. - L2GenesisBlockExtraData []byte `json:"l2GenesisBlockExtraData"` + // Note that there is no L2 genesis ExtraData, as it must default to a valid Holocene eip-1559 + // configuration. See constant 'HoloceneExtraData' for the specific value used. // Note that there is no L2 genesis timestamp: // This is instead configured based on the timestamp of "l1StartingBlockTag". } @@ -342,6 +342,9 @@ type UpgradeScheduleDeployConfig struct { // L2GenesisGraniteTimeOffset is the number of seconds after genesis block that Granite hard fork activates. // Set it to 0 to activate at genesis. Nil to disable Granite. L2GenesisGraniteTimeOffset *hexutil.Uint64 `json:"l2GenesisGraniteTimeOffset,omitempty"` + // L2GenesisHoloceneTimeOffset is the number of seconds after genesis block that the Holocene hard fork activates. + // Set it to 0 to activate at genesis. Nil to disable Holocene. + L2GenesisHoloceneTimeOffset *hexutil.Uint64 `json:"l2GenesisHoloceneTimeOffset,omitempty"` // L2GenesisInteropTimeOffset is the number of seconds after genesis block that the Interop hard fork activates. // Set it to 0 to activate at genesis. Nil to disable Interop. L2GenesisInteropTimeOffset *hexutil.Uint64 `json:"l2GenesisInteropTimeOffset,omitempty"` @@ -351,6 +354,10 @@ type UpgradeScheduleDeployConfig struct { // UseInterop is a flag that indicates if the system is using interop UseInterop bool `json:"useInterop,omitempty"` + + // L2GenesisBlobTimeOffset is the number of seconds after genesis block that the L2Blob hard fork activates. + // Set it to 0 to activate at genesis. Nil to disable L2Blob. + L2GenesisBlobTimeOffset *hexutil.Uint64 `json:"l2GenesisBlobTimeOffset,omitempty"` } var _ ConfigChecker = (*UpgradeScheduleDeployConfig)(nil) @@ -366,6 +373,81 @@ func offsetToUpgradeTime(offset *hexutil.Uint64, genesisTime uint64) *uint64 { return &v } +func (d *UpgradeScheduleDeployConfig) ForkTimeOffset(fork rollup.ForkName) *uint64 { + switch fork { + case rollup.Regolith: + return (*uint64)(d.L2GenesisRegolithTimeOffset) + case rollup.Canyon: + return (*uint64)(d.L2GenesisCanyonTimeOffset) + case rollup.Delta: + return (*uint64)(d.L2GenesisDeltaTimeOffset) + case rollup.Ecotone: + return (*uint64)(d.L2GenesisEcotoneTimeOffset) + case rollup.Fjord: + return (*uint64)(d.L2GenesisFjordTimeOffset) + case rollup.Granite: + return (*uint64)(d.L2GenesisGraniteTimeOffset) + case rollup.Holocene: + return (*uint64)(d.L2GenesisHoloceneTimeOffset) + case rollup.Interop: + return (*uint64)(d.L2GenesisInteropTimeOffset) + default: + panic(fmt.Sprintf("unknown fork: %s", fork)) + } +} + +func (d *UpgradeScheduleDeployConfig) SetForkTimeOffset(fork rollup.ForkName, offset *uint64) { + switch fork { + case rollup.Regolith: + d.L2GenesisRegolithTimeOffset = (*hexutil.Uint64)(offset) + case rollup.Canyon: + d.L2GenesisCanyonTimeOffset = (*hexutil.Uint64)(offset) + case rollup.Delta: + d.L2GenesisDeltaTimeOffset = (*hexutil.Uint64)(offset) + case rollup.Ecotone: + d.L2GenesisEcotoneTimeOffset = (*hexutil.Uint64)(offset) + case rollup.Fjord: + d.L2GenesisFjordTimeOffset = (*hexutil.Uint64)(offset) + case rollup.Granite: + d.L2GenesisGraniteTimeOffset = (*hexutil.Uint64)(offset) + case rollup.Holocene: + d.L2GenesisHoloceneTimeOffset = (*hexutil.Uint64)(offset) + case rollup.Interop: + d.L2GenesisInteropTimeOffset = (*hexutil.Uint64)(offset) + default: + panic(fmt.Sprintf("unknown fork: %s", fork)) + } +} + +var scheduleableForks = rollup.ForksFrom(rollup.Regolith) + +// ActivateForkAtOffset activates the given fork at the given offset. Previous forks are activated +// at genesis and later forks are deactivated. +// If multiple forks should be activated at a later time than genesis, first call +// ActivateForkAtOffset with the earliest fork and then SetForkTimeOffset to individually set later +// forks. +func (d *UpgradeScheduleDeployConfig) ActivateForkAtOffset(fork rollup.ForkName, offset uint64) { + if !rollup.IsValidFork(fork) || fork == rollup.Bedrock { + panic(fmt.Sprintf("invalid fork: %s", fork)) + } + ts := new(uint64) + for i, f := range scheduleableForks { + if f == fork { + d.SetForkTimeOffset(fork, &offset) + ts = nil + } else { + d.SetForkTimeOffset(scheduleableForks[i], ts) + } + } +} + +// ActivateForkAtGenesis activates the given fork, and all previous forks, at genesis. +// Later forks are deactivated. +// See also [ActivateForkAtOffset]. +func (d *UpgradeScheduleDeployConfig) ActivateForkAtGenesis(fork rollup.ForkName) { + d.ActivateForkAtOffset(fork, 0) +} + func (d *UpgradeScheduleDeployConfig) RegolithTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisRegolithTimeOffset, genesisTime) } @@ -390,12 +472,26 @@ func (d *UpgradeScheduleDeployConfig) GraniteTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisGraniteTimeOffset, genesisTime) } +func (d *UpgradeScheduleDeployConfig) HoloceneTime(genesisTime uint64) *uint64 { + return offsetToUpgradeTime(d.L2GenesisHoloceneTimeOffset, genesisTime) +} + func (d *UpgradeScheduleDeployConfig) InteropTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisInteropTimeOffset, genesisTime) } -func (d *UpgradeScheduleDeployConfig) AllocMode(genesisTime uint64) L2AllocsMode { +func (d *UpgradeScheduleDeployConfig) L2BlobTime(genesisTime uint64) *uint64 { + if d.L2GenesisBlobTimeOffset == nil { + return nil + } + v := uint64(0) + if offset := *d.L2GenesisBlobTimeOffset; offset > 0 { + v = genesisTime + uint64(offset) + } + return &v +} +func (d *UpgradeScheduleDeployConfig) AllocMode(genesisTime uint64) L2AllocsMode { forks := d.forks() for i := len(forks) - 1; i >= 0; i-- { if forkTime := offsetToUpgradeTime(forks[i].L2GenesisTimeOffset, genesisTime); forkTime != nil && *forkTime == 0 { @@ -422,6 +518,7 @@ func (d *UpgradeScheduleDeployConfig) forks() []Fork { {L2GenesisTimeOffset: d.L2GenesisEcotoneTimeOffset, Name: string(L2AllocsEcotone)}, {L2GenesisTimeOffset: d.L2GenesisFjordTimeOffset, Name: string(L2AllocsFjord)}, {L2GenesisTimeOffset: d.L2GenesisGraniteTimeOffset, Name: string(L2AllocsGranite)}, + {L2GenesisTimeOffset: d.L2GenesisHoloceneTimeOffset, Name: string(L2AllocsHolocene)}, } } @@ -515,18 +612,18 @@ func (d *L2CoreDeployConfig) Check(log log.Logger) error { // AltDADeployConfig configures optional AltDA functionality. type AltDADeployConfig struct { // UseAltDA is a flag that indicates if the system is using op-alt-da - UseAltDA bool `json:"useAltDA"` + UseAltDA bool `json:"useAltDA" toml:"useAltDA"` // DACommitmentType specifies the allowed commitment - DACommitmentType string `json:"daCommitmentType"` + DACommitmentType string `json:"daCommitmentType" toml:"daCommitmentType"` // DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged. - DAChallengeWindow uint64 `json:"daChallengeWindow"` + DAChallengeWindow uint64 `json:"daChallengeWindow" toml:"daChallengeWindow"` // DAResolveWindow represents the block interval during which a data availability challenge can be resolved. - DAResolveWindow uint64 `json:"daResolveWindow"` + DAResolveWindow uint64 `json:"daResolveWindow" toml:"daResolveWindow"` // DABondSize represents the required bond size to initiate a data availability challenge. - DABondSize uint64 `json:"daBondSize"` + DABondSize uint64 `json:"daBondSize" toml:"daBondSize"` // DAResolverRefundPercentage represents the percentage of the resolving cost to be refunded to the resolver // such as 100 means 100% refund. - DAResolverRefundPercentage uint64 `json:"daResolverRefundPercentage"` + DAResolverRefundPercentage uint64 `json:"daResolverRefundPercentage" toml:"daResolverRefundPercentage"` } var _ ConfigChecker = (*AltDADeployConfig)(nil) @@ -564,6 +661,8 @@ type L2InitializationConfig struct { UpgradeScheduleDeployConfig L2CoreDeployConfig AltDADeployConfig + SoulGasTokenConfig + InboxContractConfig } func (d *L2InitializationConfig) Check(log log.Logger) error { @@ -736,6 +835,21 @@ type L1DependenciesConfig struct { ProtocolVersionsProxy common.Address `json:"protocolVersionsProxy"` } +// SoulGasTokenConfig configures the SoulGasToken deployment to L2. +type SoulGasTokenConfig struct { + // UseSoulGasToken is a flag that indicates if the system is using SoulGasToken + UseSoulGasToken bool `json:"useSoulGasToken,omitempty"` + // IsSoulBackedByNative is a flag that indicates if the SoulGasToken is backed by native. + // Only effective when UseSoulGasToken is true. + IsSoulBackedByNative bool `json:"isSoulBackedByNative,omitempty"` +} + +// InboxContractConfig configures whether inbox contract is enabled. +// If enabled, the batcher tx will be further filtered by tx status. +type InboxContractConfig struct { + UseInboxContract bool `json:"useInboxContract,omitempty"` +} + // DependencyContext is the contextual configuration needed to verify the L1 dependencies, // used by DeployConfig.CheckAddresses. type DependencyContext struct { @@ -772,12 +886,6 @@ func (d *L1DependenciesConfig) CheckAddresses(dependencyContext DependencyContex // The genesis generation may log warnings, do a best-effort support attempt, // or ignore these attributes completely. type LegacyDeployConfig struct { - // CliqueSignerAddress represents the signer address for the clique consensus engine. - // It is used in the multi-process devnet to sign blocks. - CliqueSignerAddress common.Address `json:"cliqueSignerAddress"` - // L1UseClique represents whether or not to use the clique consensus engine. - L1UseClique bool `json:"l1UseClique"` - // DeploymentWaitConfirmations is the number of confirmations to wait during // deployment. This is DEPRECATED and should be removed in a future PR. DeploymentWaitConfirmations int `json:"deploymentWaitConfirmations"` @@ -898,6 +1006,17 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa l1StartTime := l1StartBlock.Time + var l2BlobConfig *rollup.L2BlobConfig + if d.L2GenesisBlobTimeOffset != nil { + l2BlobConfig = &rollup.L2BlobConfig{ + L2BlobTime: d.L2BlobTime(l1StartTime), + } + } + var inboxContractConfig *rollup.InboxContractConfig + if d.UseInboxContract { + inboxContractConfig = &rollup.InboxContractConfig{UseInboxContract: true} + } + return &rollup.Config{ Genesis: rollup.Genesis{ L1: eth.BlockID{ @@ -931,9 +1050,12 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa EcotoneTime: d.EcotoneTime(l1StartTime), FjordTime: d.FjordTime(l1StartTime), GraniteTime: d.GraniteTime(l1StartTime), + HoloceneTime: d.HoloceneTime(l1StartTime), InteropTime: d.InteropTime(l1StartTime), ProtocolVersionsAddress: d.ProtocolVersionsProxy, AltDAConfig: altDA, + L2BlobConfig: l2BlobConfig, + InboxContractConfig: inboxContractConfig, }, nil } diff --git a/op-chain-ops/genesis/config_test.go b/op-chain-ops/genesis/config_test.go index 3bb55e7b52dc7..47ffc46508220 100644 --- a/op-chain-ops/genesis/config_test.go +++ b/op-chain-ops/genesis/config_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/testlog" ) @@ -24,8 +25,6 @@ func TestConfigDataMarshalUnmarshal(t *testing.T) { dec := json.NewDecoder(bytes.NewReader(b)) decoded := new(DeployConfig) require.NoError(t, dec.Decode(decoded)) - require.EqualValues(t, "non-default value", string(decoded.L2GenesisBlockExtraData)) - require.NoError(t, decoded.Check(testlog.Logger(t, log.LevelDebug))) encoded, err := json.MarshalIndent(decoded, "", " ") @@ -47,7 +46,10 @@ func TestRegolithTimeZero(t *testing.T) { config := &DeployConfig{ L2InitializationConfig: L2InitializationConfig{ UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{ - L2GenesisRegolithTimeOffset: ®olithOffset}}} + L2GenesisRegolithTimeOffset: ®olithOffset, + }, + }, + } require.Equal(t, uint64(0), *config.RegolithTime(1234)) } @@ -56,7 +58,10 @@ func TestRegolithTimeAsOffset(t *testing.T) { config := &DeployConfig{ L2InitializationConfig: L2InitializationConfig{ UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{ - L2GenesisRegolithTimeOffset: ®olithOffset}}} + L2GenesisRegolithTimeOffset: ®olithOffset, + }, + }, + } require.Equal(t, uint64(1500+5000), *config.RegolithTime(5000)) } @@ -65,7 +70,10 @@ func TestCanyonTimeZero(t *testing.T) { config := &DeployConfig{ L2InitializationConfig: L2InitializationConfig{ UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{ - L2GenesisCanyonTimeOffset: &canyonOffset}}} + L2GenesisCanyonTimeOffset: &canyonOffset, + }, + }, + } require.Equal(t, uint64(0), *config.CanyonTime(1234)) } @@ -74,7 +82,10 @@ func TestCanyonTimeOffset(t *testing.T) { config := &DeployConfig{ L2InitializationConfig: L2InitializationConfig{ UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{ - L2GenesisCanyonTimeOffset: &canyonOffset}}} + L2GenesisCanyonTimeOffset: &canyonOffset, + }, + }, + } require.Equal(t, uint64(1234+1500), *config.CanyonTime(1234)) } @@ -126,3 +137,41 @@ func TestL1Deployments(t *testing.T) { // One that doesn't exist returns empty string require.Equal(t, "", deployments.GetName(common.Address{19: 0xff})) } + +// This test guarantees that getters and setters for all forks are present. +func TestUpgradeScheduleDeployConfig_ForkGettersAndSetters(t *testing.T) { + var d UpgradeScheduleDeployConfig + for i, fork := range rollup.ForksFrom(rollup.Regolith) { + require.Nil(t, d.ForkTimeOffset(fork)) + offset := uint64(i * 42) + d.SetForkTimeOffset(fork, &offset) + require.Equal(t, offset, *d.ForkTimeOffset(fork)) + } +} + +func TestUpgradeScheduleDeployConfig_ActivateForkAtOffset(t *testing.T) { + var d UpgradeScheduleDeployConfig + ts := uint64(42) + t.Run("invalid", func(t *testing.T) { + require.Panics(t, func() { d.ActivateForkAtOffset(rollup.Bedrock, ts) }) + }) + + t.Run("regolith", func(t *testing.T) { + d.ActivateForkAtOffset(rollup.Regolith, ts) + require.EqualValues(t, &ts, d.L2GenesisRegolithTimeOffset) + for _, fork := range scheduleableForks[1:] { + require.Nil(t, d.ForkTimeOffset(fork)) + } + }) + + t.Run("ecotone", func(t *testing.T) { + d.ActivateForkAtOffset(rollup.Ecotone, ts) + require.EqualValues(t, &ts, d.L2GenesisEcotoneTimeOffset) + for _, fork := range scheduleableForks[:3] { + require.Zero(t, *d.ForkTimeOffset(fork)) + } + for _, fork := range scheduleableForks[4:] { + require.Nil(t, d.ForkTimeOffset(fork)) + } + }) +} diff --git a/op-chain-ops/genesis/genesis.go b/op-chain-ops/genesis/genesis.go index dc7a44ad7ddf5..172e0861a2c4e 100644 --- a/op-chain-ops/genesis/genesis.go +++ b/op-chain-ops/genesis/genesis.go @@ -2,24 +2,23 @@ package genesis import ( "errors" - "fmt" "math/big" "time" "github.com/ethereum-optimism/optimism/op-service/predeploys" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" ) // defaultGasLimit represents the default gas limit for a genesis block. const defaultGasLimit = 30_000_000 -// BedrockTransitionBlockExtraData represents the default extra data for the bedrock transition block. -var BedrockTransitionBlockExtraData = []byte("BEDROCK") +// HoloceneExtraData represents the default extra data for Holocene-genesis chains. +var HoloceneExtraData = eip1559.EncodeHoloceneExtraData(250, 6) // NewL2Genesis will create a new L2 genesis func NewL2Genesis(config *DeployConfig, l1StartHeader *types.Header) (*core.Genesis, error) { @@ -70,11 +69,15 @@ func NewL2Genesis(config *DeployConfig, l1StartHeader *types.Header) (*core.Gene EcotoneTime: config.EcotoneTime(l1StartTime), FjordTime: config.FjordTime(l1StartTime), GraniteTime: config.GraniteTime(l1StartTime), + HoloceneTime: config.HoloceneTime(l1StartTime), InteropTime: config.InteropTime(l1StartTime), + L2BlobTime: config.L2BlobTime(l1StartTime), Optimism: ¶ms.OptimismConfig{ EIP1559Denominator: eip1559Denom, EIP1559Elasticity: eip1559Elasticity, EIP1559DenominatorCanyon: &eip1559DenomCanyon, + IsSoulBackedByNative: config.IsSoulBackedByNative, + UseSoulGasToken: config.UseSoulGasToken, }, } @@ -91,21 +94,10 @@ func NewL2Genesis(config *DeployConfig, l1StartHeader *types.Header) (*core.Gene difficulty = newHexBig(0) } - extraData := config.L2GenesisBlockExtraData - if extraData == nil { - // L2GenesisBlockExtraData is optional, so use a default value when nil - extraData = BedrockTransitionBlockExtraData - } - // Ensure that the extradata is valid - if size := len(extraData); size > 32 { - return nil, fmt.Errorf("transition block extradata too long: %d", size) - } - genesis := &core.Genesis{ Config: &optimismChainConfig, Nonce: uint64(config.L2GenesisBlockNonce), Timestamp: l1StartTime, - ExtraData: extraData, GasLimit: uint64(gasLimit), Difficulty: difficulty.ToInt(), Mixhash: config.L2GenesisBlockMixHash, @@ -121,6 +113,9 @@ func NewL2Genesis(config *DeployConfig, l1StartHeader *types.Header) (*core.Gene genesis.BlobGasUsed = u64ptr(0) genesis.ExcessBlobGas = u64ptr(0) } + if optimismChainConfig.IsHolocene(genesis.Timestamp) { + genesis.ExtraData = HoloceneExtraData + } return genesis, nil } @@ -148,25 +143,12 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { LondonBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0), - ShanghaiTime: nil, - CancunTime: nil, - } - - extraData := make([]byte, 0) - if config.L1UseClique { - // warning: clique has an overly strict block header timestamp check against the system wallclock, - // causing blocks to get scheduled as "future block" and not get mined instantly when produced. - chainConfig.Clique = ¶ms.CliqueConfig{ - Period: config.L1BlockTime, - Epoch: 30000, - } - extraData = append(append(make([]byte, 32), config.CliqueSignerAddress[:]...), make([]byte, crypto.SignatureLength)...) - } else { - chainConfig.MergeNetsplitBlock = big.NewInt(0) - chainConfig.TerminalTotalDifficulty = big.NewInt(0) - chainConfig.TerminalTotalDifficultyPassed = true - chainConfig.ShanghaiTime = u64ptr(0) - chainConfig.CancunTime = u64ptr(0) + ShanghaiTime: u64ptr(0), + CancunTime: u64ptr(0), + // To enable post-Merge consensus at genesis + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, } gasLimit := config.L1GenesisBlockGasLimit @@ -185,7 +167,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { if timestamp == 0 { timestamp = hexutil.Uint64(time.Now().Unix()) } - if !config.L1UseClique && config.L1CancunTimeOffset != nil { + if config.L1CancunTimeOffset != nil { cancunTime := uint64(timestamp) + uint64(*config.L1CancunTimeOffset) chainConfig.CancunTime = &cancunTime } @@ -194,7 +176,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { Config: &chainConfig, Nonce: uint64(config.L1GenesisBlockNonce), Timestamp: uint64(timestamp), - ExtraData: extraData, + ExtraData: make([]byte, 0), GasLimit: uint64(gasLimit), Difficulty: difficulty.ToInt(), Mixhash: config.L1GenesisBlockMixHash, diff --git a/op-chain-ops/genesis/layer_two.go b/op-chain-ops/genesis/layer_two.go index c7c9765019e24..b1c0922de1eb4 100644 --- a/op-chain-ops/genesis/layer_two.go +++ b/op-chain-ops/genesis/layer_two.go @@ -22,10 +22,11 @@ type L2AllocsMode string type L2AllocsModeMap map[L2AllocsMode]*foundry.ForgeAllocs const ( - L2AllocsDelta L2AllocsMode = "delta" - L2AllocsEcotone L2AllocsMode = "ecotone" - L2AllocsFjord L2AllocsMode = "fjord" - L2AllocsGranite L2AllocsMode = "granite" + L2AllocsDelta L2AllocsMode = "delta" + L2AllocsEcotone L2AllocsMode = "ecotone" + L2AllocsFjord L2AllocsMode = "fjord" + L2AllocsGranite L2AllocsMode = "granite" + L2AllocsHolocene L2AllocsMode = "holocene" ) var ( diff --git a/op-chain-ops/genesis/testdata/test-deploy-config-full.json b/op-chain-ops/genesis/testdata/test-deploy-config-full.json index 1d0702e0218ee..7fe9a78e71549 100644 --- a/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -6,8 +6,6 @@ "maxSequencerDrift": 20, "sequencerWindowSize": 100, "channelTimeout": 30, - "l1UseClique": false, - "cliqueSignerAddress": "0x0000000000000000000000000000000000000000", "customGasTokenAddress": "0x0000000000000000000000000000000000000000", "p2pSequencerAddress": "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc", "batchInboxAddress": "0x42000000000000000000000000000000000000ff", @@ -39,16 +37,15 @@ "l2GenesisBlockGasUsed": "0x0", "l2GenesisBlockParentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", - "l2GenesisBlockExtraData": "bm9uLWRlZmF1bHQgdmFsdWU=", "baseFeeVaultRecipient": "0x42000000000000000000000000000000000000f5", "l1FeeVaultRecipient": "0x42000000000000000000000000000000000000f6", "sequencerFeeVaultRecipient": "0x42000000000000000000000000000000000000f7", "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", - "baseFeeVaultWithdrawalNetwork": "remote", - "l1FeeVaultWithdrawalNetwork": "local", - "sequencerFeeVaultWithdrawalNetwork": "local", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 1, + "sequencerFeeVaultWithdrawalNetwork": 1, "l1StandardBridgeProxy": "0x42000000000000000000000000000000000000f8", "l1CrossDomainMessengerProxy": "0x42000000000000000000000000000000000000f9", "l1ERC721BridgeProxy": "0x4200000000000000000000000000000000000060", diff --git a/op-chain-ops/genesis/withdrawal_network.go b/op-chain-ops/genesis/withdrawal_network.go index 6aa753d36bd85..a8bb2b25ff026 100644 --- a/op-chain-ops/genesis/withdrawal_network.go +++ b/op-chain-ops/genesis/withdrawal_network.go @@ -72,3 +72,7 @@ func (w *WithdrawalNetwork) UnmarshalJSON(b []byte) error { *w = s return nil } + +func (w WithdrawalNetwork) MarshalJSON() ([]byte, error) { + return json.Marshal(int(w.ToUint8())) +} diff --git a/op-chain-ops/genesis/withdrawal_network_test.go b/op-chain-ops/genesis/withdrawal_network_test.go index c9df321296ff9..624cbc0efe72c 100644 --- a/op-chain-ops/genesis/withdrawal_network_test.go +++ b/op-chain-ops/genesis/withdrawal_network_test.go @@ -91,6 +91,12 @@ func TestWithdrawalNetworkInlineJSON(t *testing.T) { "sequencerFeeVaultWithdrawalNetwork": "local" }` + intJsonString := `{ + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 1, + "sequencerFeeVaultWithdrawalNetwork": 1 + }` + t.Run("StringMarshaling", func(t *testing.T) { decoded := new(tempNetworks) require.NoError(t, json.Unmarshal([]byte(jsonString), decoded)) @@ -101,15 +107,10 @@ func TestWithdrawalNetworkInlineJSON(t *testing.T) { encoded, err := json.Marshal(decoded) require.NoError(t, err) - require.JSONEq(t, jsonString, string(encoded)) + require.JSONEq(t, intJsonString, string(encoded)) }) t.Run("IntMarshaling", func(t *testing.T) { - intJsonString := `{ - "baseFeeVaultWithdrawalNetwork": 0, - "l1FeeVaultWithdrawalNetwork": 1, - "sequencerFeeVaultWithdrawalNetwork": 1 - }` decoded := new(tempNetworks) require.NoError(t, json.Unmarshal([]byte(intJsonString), decoded)) @@ -120,6 +121,27 @@ func TestWithdrawalNetworkInlineJSON(t *testing.T) { encoded, err := json.Marshal(decoded) require.NoError(t, err) - require.JSONEq(t, jsonString, string(encoded)) + require.JSONEq(t, intJsonString, string(encoded)) }) } + +func TestWithdrawalNetworkMarshalJSON(t *testing.T) { + type test struct { + Network WithdrawalNetwork + } + + tests := []struct { + network WithdrawalNetwork + exp string + }{ + {WithdrawalNetwork("local"), `{"Network":1}`}, + {WithdrawalNetwork("remote"), `{"Network":0}`}, + } + for _, tt := range tests { + t.Run(string(tt.network), func(t *testing.T) { + data, err := json.Marshal(test{tt.network}) + require.NoError(t, err) + require.JSONEq(t, tt.exp, string(data)) + }) + } +} diff --git a/op-chain-ops/interopgen/configs.go b/op-chain-ops/interopgen/configs.go index 942588a9929cc..948d9daa30533 100644 --- a/op-chain-ops/interopgen/configs.go +++ b/op-chain-ops/interopgen/configs.go @@ -35,7 +35,7 @@ type SuperFaultProofConfig struct { } type OPCMImplementationsConfig struct { - Release string + L1ContractsRelease string FaultProof SuperFaultProofConfig diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index bf86ccb5e1f28..e915b724e10b6 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -5,12 +5,15 @@ import ( "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis/beacondeposit" @@ -129,7 +132,7 @@ func CreateL2(logger log.Logger, fa *foundry.ArtifactsFS, srcFS *foundry.SourceM } l2Host := script.NewHost(logger.New("role", "l2", "chain", l2Cfg.L2ChainID), fa, srcFS, l2Context) l2Host.SetEnvVar("OUTPUT_MODE", "none") // we don't use the cheatcode, but capture the state outside of EVM execution - l2Host.SetEnvVar("FORK", "granite") // latest fork + l2Host.SetEnvVar("FORK", "holocene") // latest fork return l2Host } @@ -167,12 +170,11 @@ func DeploySuperchainToL1(l1Host *script.Host, superCfg *SuperchainConfig) (*Sup ProofMaturityDelaySeconds: superCfg.Implementations.FaultProof.ProofMaturityDelaySeconds, DisputeGameFinalityDelaySeconds: superCfg.Implementations.FaultProof.DisputeGameFinalityDelaySeconds, MipsVersion: superCfg.Implementations.FaultProof.MipsVersion, - Release: superCfg.Implementations.Release, + L1ContractsRelease: superCfg.Implementations.L1ContractsRelease, SuperchainConfigProxy: superDeployment.SuperchainConfigProxy, ProtocolVersionsProxy: superDeployment.ProtocolVersionsProxy, - OpcmProxyOwner: superDeployment.SuperchainProxyAdmin, UseInterop: superCfg.Implementations.UseInterop, - StandardVersionsToml: opcm.StandardVersionsMainnetData, + StandardVersionsToml: standard.VersionsMainnetData, }) if err != nil { return nil, fmt.Errorf("failed to deploy Implementations contracts: %w", err) @@ -197,7 +199,7 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme l1Host.SetTxOrigin(cfg.Deployer) - output, err := opcm.DeployOPChain(l1Host, opcm.DeployOPChainInput{ + output, err := opcm.DeployOPChainV160(l1Host, opcm.DeployOPChainInputV160{ OpChainProxyAdminOwner: cfg.ProxyAdminOwner, SystemConfigOwner: cfg.SystemConfigOwner, Batcher: cfg.BatchSenderAddress, @@ -207,7 +209,7 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme BasefeeScalar: cfg.GasPriceOracleBaseFeeScalar, BlobBaseFeeScalar: cfg.GasPriceOracleBlobBaseFeeScalar, L2ChainId: new(big.Int).SetUint64(cfg.L2ChainID), - OpcmProxy: superDeployment.OpcmProxy, + Opcm: superDeployment.Opcm, SaltMixer: cfg.SaltMixer, GasLimit: cfg.GasLimit, DisputeGameType: cfg.DisputeGameType, diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index ba18fbfdf9bde..f98a0554d8709 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -9,8 +9,7 @@ type L1Deployment struct { } type Implementations struct { - OpcmProxy common.Address `json:"OPCMProxy"` - OpcmImpl common.Address `json:"OPCMImpl"` + Opcm common.Address `json:"OPCM"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` OptimismPortalImpl common.Address `json:"OptimismPortalImpl"` PreimageOracleSingleton common.Address `json:"PreimageOracleSingleton"` diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index fa7c342672018..e70c69e9f481a 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -4,11 +4,12 @@ import ( "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" ) @@ -68,7 +69,7 @@ func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) ProtocolVersionsOwner: superchainProtocolVersionsOwner, Deployer: superchainDeployer, Implementations: OPCMImplementationsConfig{ - Release: "dev", + L1ContractsRelease: "dev", FaultProof: SuperFaultProofConfig{ WithdrawalDelaySeconds: big.NewInt(604800), MinProposalSizeBytes: big.NewInt(10000), @@ -78,7 +79,7 @@ func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) MipsVersion: big.NewInt(1), }, UseInterop: true, - StandardVersionsToml: opcm.StandardVersionsMainnetData, + StandardVersionsToml: standard.VersionsMainnetData, }, SuperchainL1DeployConfig: genesis.SuperchainL1DeployConfig{ RequiredProtocolVersion: params.OPStackSupport, @@ -108,6 +109,7 @@ func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses l1Cfg.Prefund[l2Cfg.BatchSenderAddress] = Ether(10_000_000) l1Cfg.Prefund[l2Cfg.Deployer] = Ether(10_000_000) l1Cfg.Prefund[l2Cfg.FinalSystemOwner] = Ether(10_000_000) + l1Cfg.Prefund[l2Cfg.SystemConfigOwner] = Ether(10_000_000) proposer, err := addrs.Address(devkeys.ChainOperatorKey{ ChainID: new(big.Int).SetUint64(l2Cfg.L2ChainID), Role: devkeys.ProposerRole, @@ -187,7 +189,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* FundDevAccounts: true, }, L2GenesisBlockDeployConfig: genesis.L2GenesisBlockDeployConfig{ - L2GenesisBlockGasLimit: 30_000_000, + L2GenesisBlockGasLimit: 60_000_000, L2GenesisBlockBaseFeePerGas: (*hexutil.Big)(big.NewInt(params.InitialBaseFee)), }, OwnershipDeployConfig: genesis.OwnershipDeployConfig{ @@ -231,6 +233,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* L2GenesisEcotoneTimeOffset: new(hexutil.Uint64), L2GenesisFjordTimeOffset: new(hexutil.Uint64), L2GenesisGraniteTimeOffset: new(hexutil.Uint64), + L2GenesisHoloceneTimeOffset: new(hexutil.Uint64), L2GenesisInteropTimeOffset: new(hexutil.Uint64), L1CancunTimeOffset: new(hexutil.Uint64), UseInterop: true, @@ -252,7 +255,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* }, Prefund: make(map[common.Address]*big.Int), SaltMixer: "", - GasLimit: 30_000_000, + GasLimit: 60_000_000, DisputeGameType: 1, // PERMISSIONED_CANNON Game Type DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), DisputeMaxGameDepth: 73, diff --git a/op-chain-ops/justfile b/op-chain-ops/justfile new file mode 100644 index 0000000000000..a9c2bcad62da0 --- /dev/null +++ b/op-chain-ops/justfile @@ -0,0 +1,41 @@ +import '../just/go.just' + +# Build ldflags string +_VERSION_META_STR := if VERSION_META != "" { "+" + VERSION_META } else { "" } +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Version=" + VERSION + " " + \ + "-X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Meta=" + _VERSION_META_STR + " " + \ + "") + "'" + +# Build ecotone-scalar binary +ecotone-scalar: (go_build "./bin/ecotone-scalar" "./cmd/ecotone-scalar" "-ldflags" _LDFLAGSSTRING) + +# Build receipt-reference-builder binary +receipt-reference-builder: (go_build "./bin/receipt-reference-builder" "./cmd/receipt-reference-builder" "-ldflags" _LDFLAGSSTRING) + +# Run tests +test: (go_test "./...") + +# Build op-deployer binary +op-deployer: + just ../op-deployer/build + mkdir -p ./bin && ln -f ../op-deployer/bin/op-deployer ./bin/op-deployer + +# Run fuzzing tests +[private] +fuzz_task FUZZ TIME='10s': (go_fuzz FUZZ TIME "./crossdomain") + +fuzz: + printf "%s\n" \ + "FuzzEncodeDecodeWithdrawal" \ + "FuzzEncodeDecodeLegacyWithdrawal" \ + "FuzzAliasing" \ + "FuzzVersionedNonce" \ + | parallel -j {{PARALLEL_JOBS}} {{just_executable()}} fuzz_task {} + +# Sync standard versions +sync-standard-version: + curl -Lo ./deployer/opcm/standard-versions-mainnet.toml https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/validation/standard/standard-versions-mainnet.toml + curl -Lo ./deployer/opcm/standard-versions-sepolia.toml https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/validation/standard/standard-versions-sepolia.toml diff --git a/op-chain-ops/script/addresses/addresses.go b/op-chain-ops/script/addresses/addresses.go new file mode 100644 index 0000000000000..76895e203e376 --- /dev/null +++ b/op-chain-ops/script/addresses/addresses.go @@ -0,0 +1,20 @@ +package addresses + +import "github.com/ethereum/go-ethereum/common" + +var ( + // DefaultSenderAddr is known as DEFAULT_SENDER = address(uint160(uint256(keccak256("foundry default caller")))) + DefaultSenderAddr = common.HexToAddress("0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38") + // DefaultScriptAddr is the address of the initial executing script, computed from: + // cast compute-address --nonce 1 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 + DefaultScriptAddr = common.HexToAddress("0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496") + // VMAddr is known as VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + VMAddr = common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D") + // ConsoleAddr is known as CONSOLE, "console.log" in ascii. + // Utils like console.sol and console2.sol work by executing a staticcall to this address. + ConsoleAddr = common.HexToAddress("0x000000000000000000636F6e736F6c652e6c6f67") + // ScriptDeployer is used for temporary scripts address(uint160(uint256(keccak256("op-stack script deployer")))) + ScriptDeployer = common.HexToAddress("0x76Ce131128F3616871f8CDA86d18fAB44E4d0D8B") + // ForgeDeployer is used by some scripts as a default deployer address, e.g. makeAddr("deployer") + ForgeDeployer = common.HexToAddress("0xaE0bDc4eEAC5E950B67C6819B118761CaAF61946") +) diff --git a/op-chain-ops/script/cheatcodes.go b/op-chain-ops/script/cheatcodes.go index 07755e527314b..46e2bc4e314c4 100644 --- a/op-chain-ops/script/cheatcodes.go +++ b/op-chain-ops/script/cheatcodes.go @@ -1,8 +1,45 @@ package script +import ( + "fmt" + + "github.com/ethereum/go-ethereum/core/vm" +) + // CheatCodesPrecompile implements the Forge vm cheatcodes. // Note that forge-std wraps these cheatcodes, // and provides additional convenience functions that use these cheatcodes. type CheatCodesPrecompile struct { h *Host } + +// AccessControlledPrecompile wraps a precompile, +// and checks that the caller has cheatcode access. +type AccessControlledPrecompile struct { + h *Host + inner vm.PrecompiledContract +} + +var _ vm.PrecompiledContract = (*AccessControlledPrecompile)(nil) + +func (c *AccessControlledPrecompile) RequiredGas(input []byte) uint64 { + // call-frame is not open yet, and prank is ignored for cheatcode access-checking. + accessor := c.h.SelfAddress() + _, ok := c.h.allowedCheatcodes[accessor] + if !ok { + // Don't just return infinite gas, we can allow it to run, + // and then revert with a proper error message. + return 0 + } + return c.inner.RequiredGas(input) +} + +func (c *AccessControlledPrecompile) Run(input []byte) ([]byte, error) { + // call-frame is not open yet, and prank is ignored for cheatcode access-checking. + accessor := c.h.SelfAddress() + if !c.h.AllowedCheatcodes(accessor) { + c.h.log.Error("Cheatcode access denied!", "caller", accessor, "label", c.h.labels[accessor]) + return encodeRevert(fmt.Errorf("call by %s to cheatcode precompile is not allowed", accessor)) + } + return c.inner.Run(input) +} diff --git a/op-chain-ops/script/cheatcodes_environment.go b/op-chain-ops/script/cheatcodes_environment.go index 92baa429010eb..55584c835db05 100644 --- a/op-chain-ops/script/cheatcodes_environment.go +++ b/op-chain-ops/script/cheatcodes_environment.go @@ -67,6 +67,10 @@ func (c *CheatCodesPrecompile) Load(account common.Address, slot [32]byte) [32]b // Etch implements https://book.getfoundry.sh/cheatcodes/etch func (c *CheatCodesPrecompile) Etch(who common.Address, code []byte) { c.h.state.SetCode(who, bytes.Clone(code)) // important to clone; geth EVM will reuse the calldata memory. + if len(code) > 0 { + // if we're not just zeroing out the account: allow it to access cheatcodes + c.h.AllowCheatcodes(who) + } } // Deal implements https://book.getfoundry.sh/cheatcodes/deal diff --git a/op-chain-ops/script/cheatcodes_forking.go b/op-chain-ops/script/cheatcodes_forking.go new file mode 100644 index 0000000000000..44ba66f8a9582 --- /dev/null +++ b/op-chain-ops/script/cheatcodes_forking.go @@ -0,0 +1,161 @@ +package script + +import ( + "errors" + "fmt" + "math/big" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" +) + +func (c *CheatCodesPrecompile) CreateFork_31ba3498(urlOrAlias string) (*big.Int, error) { + return c.createFork(ForkWithURLOrAlias(urlOrAlias)) +} + +func (c *CheatCodesPrecompile) CreateFork_6ba3ba2b(urlOrAlias string, block *big.Int) (*big.Int, error) { + return c.createFork(ForkWithURLOrAlias(urlOrAlias), ForkWithBlockNumberU256(block)) +} + +func (c *CheatCodesPrecompile) CreateFork_7ca29682(urlOrAlias string, txHash common.Hash) (*big.Int, error) { + return c.createFork(ForkWithURLOrAlias(urlOrAlias), ForkWithTransaction(txHash)) +} + +// createFork implements vm.createFork: +// https://book.getfoundry.sh/cheatcodes/create-fork +func (c *CheatCodesPrecompile) createFork(opts ...ForkOption) (*big.Int, error) { + src, err := c.h.onFork(opts...) + if err != nil { + return nil, fmt.Errorf("failed to setup fork source: %w", err) + } + id, err := c.h.state.CreateFork(src) + if err != nil { + return nil, fmt.Errorf("failed to create fork: %w", err) + } + return id.U256().ToBig(), nil +} + +func (c *CheatCodesPrecompile) CreateSelectFork_98680034(urlOrAlias string) (*big.Int, error) { + return c.createSelectFork(ForkWithURLOrAlias(urlOrAlias)) +} + +func (c *CheatCodesPrecompile) CreateSelectFork_71ee464d(urlOrAlias string, block *big.Int) (*big.Int, error) { + return c.createSelectFork(ForkWithURLOrAlias(urlOrAlias), ForkWithBlockNumberU256(block)) +} + +func (c *CheatCodesPrecompile) CreateSelectFork_84d52b7a(urlOrAlias string, txHash common.Hash) (*big.Int, error) { + return c.createSelectFork(ForkWithURLOrAlias(urlOrAlias), ForkWithTransaction(txHash)) +} + +// createSelectFork implements vm.createSelectFork: +// https://book.getfoundry.sh/cheatcodes/create-select-fork +func (c *CheatCodesPrecompile) createSelectFork(opts ...ForkOption) (*big.Int, error) { + return c.h.CreateSelectFork(opts...) +} + +// ActiveFork implements vm.activeFork: +// https://book.getfoundry.sh/cheatcodes/active-fork +func (c *CheatCodesPrecompile) ActiveFork() (*uint256.Int, error) { + id, active := c.h.state.ActiveFork() + if !active { + return nil, errors.New("no active fork") + } + return id.U256(), nil +} + +// convenience method, to repeat the same URLOrAlias as the given fork when setting up a new fork +func (c *CheatCodesPrecompile) forkURLOption(id forking.ForkID) ForkOption { + return func(cfg *ForkConfig) error { + urlOrAlias, err := c.h.state.ForkURLOrAlias(id) + if err != nil { + return err + } + return ForkWithURLOrAlias(urlOrAlias)(cfg) + } +} + +func (c *CheatCodesPrecompile) RollFork_d9bbf3a1(block *big.Int) error { + id, ok := c.h.state.ActiveFork() + if !ok { + return errors.New("no active fork") + } + return c.rollFork(id, c.forkURLOption(id), ForkWithBlockNumberU256(block)) +} + +func (c *CheatCodesPrecompile) RollFork_0f29772b(txHash common.Hash) error { + id, ok := c.h.state.ActiveFork() + if !ok { + return errors.New("no active fork") + } + return c.rollFork(id, c.forkURLOption(id), ForkWithTransaction(txHash)) +} + +func (c *CheatCodesPrecompile) RollFork_d74c83a4(forkID *big.Int, block *big.Int) error { + id := forking.ForkIDFromBig(forkID) + return c.rollFork(id, c.forkURLOption(id), ForkWithBlockNumberU256(block)) +} + +func (c *CheatCodesPrecompile) RollFork_f2830f7b(forkID *uint256.Int, txHash common.Hash) error { + id := forking.ForkID(*forkID) + return c.rollFork(id, c.forkURLOption(id), ForkWithTransaction(txHash)) +} + +// rollFork implements vm.rollFork: +// https://book.getfoundry.sh/cheatcodes/roll-fork +func (c *CheatCodesPrecompile) rollFork(id forking.ForkID, opts ...ForkOption) error { + src, err := c.h.onFork(opts...) + if err != nil { + return fmt.Errorf("cannot setup fork source for roll-fork change: %w", err) + } + return c.h.state.ResetFork(id, src) +} + +// MakePersistent_57e22dde implements vm.makePersistent: +// https://book.getfoundry.sh/cheatcodes/make-persistent +func (c *CheatCodesPrecompile) MakePersistent_57e22dde(account0 common.Address) { + c.h.state.MakePersistent(account0) +} + +func (c *CheatCodesPrecompile) MakePersistent_4074e0a8(account0, account1 common.Address) { + c.h.state.MakePersistent(account0) + c.h.state.MakePersistent(account1) +} + +func (c *CheatCodesPrecompile) MakePersistent_efb77a75(account0, account1, account2 common.Address) { + c.h.state.MakePersistent(account0) + c.h.state.MakePersistent(account1) + c.h.state.MakePersistent(account2) +} + +func (c *CheatCodesPrecompile) MakePersistent_1d9e269e(accounts []common.Address) { + for _, addr := range accounts { + c.h.state.MakePersistent(addr) + } +} + +// RevokePersistent_997a0222 implements vm.revokePersistent: +// https://book.getfoundry.sh/cheatcodes/revoke-persistent +func (c *CheatCodesPrecompile) RevokePersistent_997a0222(addr common.Address) { + c.h.state.RevokePersistent(addr) +} + +func (c *CheatCodesPrecompile) RevokePersistent_3ce969e6(addrs []common.Address) { + for _, addr := range addrs { + c.h.state.RevokePersistent(addr) + } +} + +// IsPersistent implements vm.isPersistent: +// https://book.getfoundry.sh/cheatcodes/is-persistent +func (c *CheatCodesPrecompile) IsPersistent(addr common.Address) bool { + return c.h.state.IsPersistent(addr) +} + +// AllowCheatcodes implements vm.allowCheatcodes: +// https://book.getfoundry.sh/cheatcodes/allow-cheatcodes +func (c *CheatCodesPrecompile) AllowCheatcodes(addr common.Address) { + c.h.AllowCheatcodes(addr) +} diff --git a/op-chain-ops/script/cheatcodes_utilities.go b/op-chain-ops/script/cheatcodes_utilities.go index 022befa606275..4f7606d7e4fba 100644 --- a/op-chain-ops/script/cheatcodes_utilities.go +++ b/op-chain-ops/script/cheatcodes_utilities.go @@ -233,6 +233,17 @@ func (c *CheatCodesPrecompile) ParseTomlAddress_65e7c844(tomlStr string, key str panic("should never get here") } +func (c *CheatCodesPrecompile) ComputeCreate2Address_890c283b(salt, codeHash [32]byte) (common.Address, error) { + data := make([]byte, 1+20+32+32) + data[0] = 0xff + copy(data[1:], DeterministicDeployerAddress.Bytes()) + copy(data[1+20:], salt[:]) + copy(data[1+20+32:], codeHash[:]) + finalHash := crypto.Keccak256(data) + // Take the last 20 bytes of the hash to get the address + return common.BytesToAddress(finalHash[12:]), nil +} + // unsupported //func (c *CheatCodesPrecompile) CreateWallet() {} diff --git a/op-chain-ops/script/cheatcodes_utilities_test.go b/op-chain-ops/script/cheatcodes_utilities_test.go index 23936a10e344e..4870ec8129dbd 100644 --- a/op-chain-ops/script/cheatcodes_utilities_test.go +++ b/op-chain-ops/script/cheatcodes_utilities_test.go @@ -57,3 +57,14 @@ func TestParseTomlAddress(t *testing.T) { require.NoError(t, err) require.Equal(t, common.HexToAddress("0xff4ce7b6a91a35c31d7d62b327d19617c8da6f23"), addr) } + +func TestComputeCreate2Address(t *testing.T) { + c := &CheatCodesPrecompile{} + var salt [32]byte + salt[31] = 'S' + var codeHash [32]byte + codeHash[31] = 'C' + addr, err := c.ComputeCreate2Address_890c283b(salt, codeHash) + require.NoError(t, err) + require.EqualValues(t, common.HexToAddress("0x2f29AF1b5a7083bf98C4A89976c2f17FF980735f"), addr) +} diff --git a/op-chain-ops/script/console_test.go b/op-chain-ops/script/console_test.go index d67f5efc0f5f5..70b78828b5d68 100644 --- a/op-chain-ops/script/console_test.go +++ b/op-chain-ops/script/console_test.go @@ -7,6 +7,8 @@ import ( "math/rand" // nosemgrep "testing" + "github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" @@ -62,8 +64,8 @@ func TestFormatter(t *testing.T) { require.Equal(t, "4.2", consoleFormat("%8e", big.NewInt(420000000))) require.Equal(t, "foo true bar false", consoleFormat("foo %s bar %s", true, false)) require.Equal(t, "foo 1 bar 0", consoleFormat("foo %d bar %d", true, false)) - require.Equal(t, "sender: "+DefaultSenderAddr.String(), - consoleFormat("sender: %s", DefaultSenderAddr)) + require.Equal(t, "sender: "+addresses.DefaultSenderAddr.String(), + consoleFormat("sender: %s", addresses.DefaultSenderAddr)) require.Equal(t, "long 0.000000000000000042 number", consoleFormat("long %18e number", big.NewInt(42))) require.Equal(t, "long 4200.000000000000000003 number", consoleFormat("long %18e number", new(big.Int).Add(new(big.Int).Mul( diff --git a/op-chain-ops/script/context.go b/op-chain-ops/script/context.go index a7a8fb7eb46a7..fbb3704688e3f 100644 --- a/op-chain-ops/script/context.go +++ b/op-chain-ops/script/context.go @@ -3,22 +3,9 @@ package script import ( "math/big" - "github.com/ethereum/go-ethereum/common" -) + "github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses" -var ( - // DefaultSenderAddr is known as DEFAULT_SENDER = address(uint160(uint256(keccak256("foundry default caller")))) - DefaultSenderAddr = common.HexToAddress("0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38") - // DefaultScriptAddr is the address of the initial executing script, computed from: - // cast compute-address --nonce 1 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 - DefaultScriptAddr = common.HexToAddress("0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496") - // VMAddr is known as VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); - VMAddr = common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D") - // ConsoleAddr is known as CONSOLE, "console.log" in ascii. - // Utils like console.sol and console2.sol work by executing a staticcall to this address. - ConsoleAddr = common.HexToAddress("0x000000000000000000636F6e736F6c652e6c6f67") - // ScriptDeployer is used for temporary scripts address(uint160(uint256(keccak256("op-stack script deployer")))) - ScriptDeployer = common.HexToAddress("0x76Ce131128F3616871f8CDA86d18fAB44E4d0D8B") + "github.com/ethereum/go-ethereum/common" ) const ( @@ -40,8 +27,8 @@ type Context struct { var DefaultContext = Context{ ChainID: big.NewInt(1337), - Sender: DefaultSenderAddr, - Origin: DefaultSenderAddr, + Sender: addresses.DefaultSenderAddr, + Origin: addresses.DefaultSenderAddr, FeeRecipient: common.Address{}, GasLimit: DefaultFoundryGasLimit, BlockNum: 0, diff --git a/op-chain-ops/script/fork.go b/op-chain-ops/script/fork.go new file mode 100644 index 0000000000000..8954a9984a35c --- /dev/null +++ b/op-chain-ops/script/fork.go @@ -0,0 +1,60 @@ +package script + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" + "github.com/ethereum/go-ethereum/common" +) + +// ForkOption modifies a ForkConfig, and can be used by Host internals, +// like the forking cheatcodes, to customize the forking action. +type ForkOption func(cfg *ForkConfig) error + +// ForkHook is a callback to the user of the Host, +// to translate an intent to fork into a source of data that can be forked with. +type ForkHook func(opts *ForkConfig) (forking.ForkSource, error) + +// ForkConfig is a bundle of data to express a fork intent +type ForkConfig struct { + URLOrAlias string + BlockNumber *uint64 // latest if nil + Transaction *common.Hash // up to pre-state of given transaction +} + +func ForkWithURLOrAlias(urlOrAlias string) ForkOption { + return func(cfg *ForkConfig) error { + cfg.URLOrAlias = urlOrAlias + return nil + } +} + +func ForkWithBlockNumberU256(num *big.Int) ForkOption { + return func(cfg *ForkConfig) error { + if !num.IsUint64() { + return fmt.Errorf("block number %s is too large", num.String()) + } + v := num.Uint64() + cfg.BlockNumber = &v + return nil + } +} + +func ForkWithTransaction(txHash common.Hash) ForkOption { + return func(cfg *ForkConfig) error { + cfg.Transaction = &txHash + return nil + } +} + +// onFork is called by script-internals to translate a fork-intent into forks data-source. +func (h *Host) onFork(opts ...ForkOption) (forking.ForkSource, error) { + cfg := &ForkConfig{} + for _, opt := range opts { + if err := opt(cfg); err != nil { + return nil, err + } + } + return h.hooks.OnFork(cfg) +} diff --git a/op-chain-ops/script/forking/cache.go b/op-chain-ops/script/forking/cache.go new file mode 100644 index 0000000000000..c9fec7bda96f5 --- /dev/null +++ b/op-chain-ops/script/forking/cache.go @@ -0,0 +1,103 @@ +package forking + +import ( + lru "github.com/hashicorp/golang-lru/v2" + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" +) + +type storageKey struct { + Addr common.Address + Slot common.Hash +} + +// CachedSource wraps a ForkSource, and caches the retrieved data for faster repeat-queries. +// The ForkSource should be immutable (as per the StateRoot value). +// All cache data accumulates in-memory in LRU collections per data type. +type CachedSource struct { + stateRoot common.Hash + src ForkSource + + nonces *lru.Cache[common.Address, uint64] + balances *lru.Cache[common.Address, *uint256.Int] + storage *lru.Cache[storageKey, common.Hash] + code *lru.Cache[common.Address, []byte] +} + +var _ ForkSource = (*CachedSource)(nil) + +func mustNewLRU[K comparable, V any](size int) *lru.Cache[K, V] { + out, err := lru.New[K, V](size) + if err != nil { + panic(err) // bad size parameter may produce an error + } + return out +} + +func Cache(src ForkSource) *CachedSource { + return &CachedSource{ + stateRoot: src.StateRoot(), + src: src, + nonces: mustNewLRU[common.Address, uint64](1000), + balances: mustNewLRU[common.Address, *uint256.Int](1000), + storage: mustNewLRU[storageKey, common.Hash](1000), + code: mustNewLRU[common.Address, []byte](100), + } +} + +func (c *CachedSource) URLOrAlias() string { + return c.src.URLOrAlias() +} + +func (c *CachedSource) StateRoot() common.Hash { + return c.stateRoot +} + +func (c *CachedSource) Nonce(addr common.Address) (uint64, error) { + if v, ok := c.nonces.Get(addr); ok { + return v, nil + } + v, err := c.src.Nonce(addr) + if err != nil { + return 0, err + } + c.nonces.Add(addr, v) + return v, nil +} + +func (c *CachedSource) Balance(addr common.Address) (*uint256.Int, error) { + if v, ok := c.balances.Get(addr); ok { + return v.Clone(), nil + } + v, err := c.src.Balance(addr) + if err != nil { + return nil, err + } + c.balances.Add(addr, v) + return v.Clone(), nil +} + +func (c *CachedSource) StorageAt(addr common.Address, key common.Hash) (common.Hash, error) { + if v, ok := c.storage.Get(storageKey{Addr: addr, Slot: key}); ok { + return v, nil + } + v, err := c.src.StorageAt(addr, key) + if err != nil { + return common.Hash{}, err + } + c.storage.Add(storageKey{Addr: addr, Slot: key}, v) + return v, nil +} + +func (c *CachedSource) Code(addr common.Address) ([]byte, error) { + if v, ok := c.code.Get(addr); ok { + return v, nil + } + v, err := c.src.Code(addr) + if err != nil { + return nil, err + } + c.code.Add(addr, v) + return v, nil +} diff --git a/op-chain-ops/script/forking/cache_test.go b/op-chain-ops/script/forking/cache_test.go new file mode 100644 index 0000000000000..7ed4f97450421 --- /dev/null +++ b/op-chain-ops/script/forking/cache_test.go @@ -0,0 +1,196 @@ +package forking + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// MockForkSource implements ForkSource interface for testing +type MockForkSource struct { + mock.Mock +} + +func (m *MockForkSource) URLOrAlias() string { + args := m.Called() + return args.String(0) +} + +func (m *MockForkSource) StateRoot() common.Hash { + args := m.Called() + return args.Get(0).(common.Hash) +} + +func (m *MockForkSource) Nonce(addr common.Address) (uint64, error) { + args := m.Called(addr) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockForkSource) Balance(addr common.Address) (*uint256.Int, error) { + args := m.Called(addr) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*uint256.Int), args.Error(1) +} + +func (m *MockForkSource) StorageAt(addr common.Address, key common.Hash) (common.Hash, error) { + args := m.Called(addr, key) + return args.Get(0).(common.Hash), args.Error(1) +} + +func (m *MockForkSource) Code(addr common.Address) ([]byte, error) { + args := m.Called(addr) + return args.Get(0).([]byte), args.Error(1) +} + +func setupCache(t *testing.T) (*CachedSource, *MockForkSource) { + mockSource := new(MockForkSource) + stateRoot := common.HexToHash("0x1234") + mockSource.On("StateRoot").Return(stateRoot) + mockSource.On("URLOrAlias").Return("test_source") + + cached := Cache(mockSource) + require.NotNil(t, cached) + require.Equal(t, stateRoot, cached.StateRoot()) + require.Equal(t, "test_source", cached.URLOrAlias()) + + return cached, mockSource +} + +func TestCachedSource_Nonce(t *testing.T) { + cached, mockSource := setupCache(t) + addr := common.HexToAddress("0x1234") + expectedNonce := uint64(42) + + // First call should hit the source + mockSource.On("Nonce", addr).Return(expectedNonce, nil).Once() + + nonce, err := cached.Nonce(addr) + require.NoError(t, err) + require.Equal(t, expectedNonce, nonce) + + // Second call should use cache + nonce, err = cached.Nonce(addr) + require.NoError(t, err) + require.Equal(t, expectedNonce, nonce) + + mockSource.AssertNumberOfCalls(t, "Nonce", 1) +} + +func TestCachedSource_Balance(t *testing.T) { + cached, mockSource := setupCache(t) + addr := common.HexToAddress("0x5678") + expectedBalance := uint256.NewInt(1000) + + // First call should hit the source + mockSource.On("Balance", addr).Return(expectedBalance, nil).Once() + + balance, err := cached.Balance(addr) + require.NoError(t, err) + require.Equal(t, expectedBalance, balance) + + // Second call should use cache + balance, err = cached.Balance(addr) + require.NoError(t, err) + require.Equal(t, expectedBalance, balance) + + // Verify the returned balance is a clone + balance.Add(balance, uint256.NewInt(1)) + cachedBalance, _ := cached.Balance(addr) + require.Equal(t, expectedBalance, cachedBalance) + + mockSource.AssertNumberOfCalls(t, "Balance", 1) +} + +func TestCachedSource_Storage(t *testing.T) { + cached, mockSource := setupCache(t) + addr := common.HexToAddress("0x9abc") + slot := common.HexToHash("0xdef0") + expectedValue := common.HexToHash("0x1234") + + // First call should hit the source + mockSource.On("StorageAt", addr, slot).Return(expectedValue, nil).Once() + + value, err := cached.StorageAt(addr, slot) + require.NoError(t, err) + require.Equal(t, expectedValue, value) + + // Second call should use cache + value, err = cached.StorageAt(addr, slot) + require.NoError(t, err) + require.Equal(t, expectedValue, value) + + mockSource.AssertNumberOfCalls(t, "StorageAt", 1) +} + +func TestCachedSource_Code(t *testing.T) { + cached, mockSource := setupCache(t) + addr := common.HexToAddress("0xdef0") + expectedCode := []byte{1, 2, 3, 4} + + // First call should hit the source + mockSource.On("Code", addr).Return(expectedCode, nil).Once() + + code, err := cached.Code(addr) + require.NoError(t, err) + require.Equal(t, expectedCode, code) + + // Second call should use cache + code, err = cached.Code(addr) + require.NoError(t, err) + require.Equal(t, expectedCode, code) + + mockSource.AssertNumberOfCalls(t, "Code", 1) +} + +func TestCachedSource_CacheEviction(t *testing.T) { + cached, mockSource := setupCache(t) + + // Test nonce cache eviction + for i := 0; i < 1001; i++ { // Cache size is 1000 + addr := common.BigToAddress(big.NewInt(int64(i))) + mockSource.On("Nonce", addr).Return(uint64(i), nil).Once() + _, _ = cached.Nonce(addr) + } + + // This should cause first address to be evicted + firstAddr := common.BytesToAddress([]byte{0}) + mockSource.On("Nonce", firstAddr).Return(uint64(0), nil).Once() + _, _ = cached.Nonce(firstAddr) + + mockSource.AssertNumberOfCalls(t, "Nonce", 1002) // 1001 + 1 for evicted key +} + +func TestCachedSource_MultipleStorageSlots(t *testing.T) { + cached, mockSource := setupCache(t) + addr := common.HexToAddress("0xabcd") + slot1 := common.HexToHash("0x1111") + slot2 := common.HexToHash("0x2222") + value1 := common.HexToHash("0x3333") + value2 := common.HexToHash("0x4444") + + mockSource.On("StorageAt", addr, slot1).Return(value1, nil).Once() + mockSource.On("StorageAt", addr, slot2).Return(value2, nil).Once() + + // Different slots should trigger separate cache entries + val1, err := cached.StorageAt(addr, slot1) + require.NoError(t, err) + require.Equal(t, value1, val1) + + val2, err := cached.StorageAt(addr, slot2) + require.NoError(t, err) + require.Equal(t, value2, val2) + + // Verify both are cached + val1Again, _ := cached.StorageAt(addr, slot1) + val2Again, _ := cached.StorageAt(addr, slot2) + require.Equal(t, value1, val1Again) + require.Equal(t, value2, val2Again) + + mockSource.AssertNumberOfCalls(t, "StorageAt", 2) +} diff --git a/op-chain-ops/script/forking/db.go b/op-chain-ops/script/forking/db.go new file mode 100644 index 0000000000000..0a073500d247c --- /dev/null +++ b/op-chain-ops/script/forking/db.go @@ -0,0 +1,119 @@ +package forking + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/ethereum/go-ethereum/triedb" +) + +// ForkDB is a virtual state database: it wraps a forked accounts trie, +// and can maintain a state diff, so we can mutate the forked state, +// and even finalize state changes (so we can accurately measure things like cold storage gas cost). +type ForkDB struct { + active *ForkedAccountsTrie +} + +// Reader for read-only access to a known state. All cold reads go through this. +// So the state-DB creates one initially, and then holds on to it. +// The diff will be overlayed on the reader still. To get rid of the diff, it has to be explicitly cleared. +// Warning: diffs applied to the original state that the reader wraps will be visible. +// Geth StateDB is meant to be reinitialized after committing state. +func (f *ForkDB) Reader(root common.Hash) (state.Reader, error) { + if root != f.active.stateRoot { + return nil, fmt.Errorf("current state is at %s, cannot open state at %s", f.active.stateRoot, root) + } + return &forkStateReader{ + f.active, + }, nil +} + +func (f *ForkDB) Snapshot() *snapshot.Tree { + return nil +} + +var _ state.Database = (*ForkDB)(nil) + +func NewForkDB(source ForkSource) *ForkDB { + return &ForkDB{active: &ForkedAccountsTrie{ + stateRoot: source.StateRoot(), + src: source, + diff: NewExportDiff(), + }} +} + +// fakeRoot is just a marker; every account we load into the fork-db has this storage-root. +// When opening a storage-trie, we sanity-check we have this root, or an empty trie. +// And then just return the same global trie view for storage reads/writes. +// It needs to be set to EmptyRootHash to avoid contract collision errors when +// deploying contracts, since Geth checks the storage root prior to deployment. +var fakeRoot = types.EmptyRootHash + +func (f *ForkDB) OpenTrie(root common.Hash) (state.Trie, error) { + if f.active.stateRoot != root { + return nil, fmt.Errorf("active fork is at %s, but tried to open %s", f.active.stateRoot, root) + } + return f.active, nil +} + +func (f *ForkDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie state.Trie) (state.Trie, error) { + if f.active.stateRoot != stateRoot { + return nil, fmt.Errorf("active fork is at %s, but tried to open account %s of state %s", f.active.stateRoot, address, stateRoot) + } + if _, ok := trie.(*ForkedAccountsTrie); !ok { + return nil, fmt.Errorf("ForkDB tried to open non-fork storage-trie %v", trie) + } + if root != fakeRoot && root != types.EmptyRootHash { + return nil, fmt.Errorf("ForkDB unexpectedly was queried with real looking storage root: %s", root) + } + return f.active, nil +} + +func (f *ForkDB) CopyTrie(trie state.Trie) state.Trie { + if st, ok := trie.(*ForkedAccountsTrie); ok { + return st.Copy() + } + panic(fmt.Errorf("ForkDB tried to copy non-fork trie %v", trie)) +} + +func (f *ForkDB) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) { + return f.active.ContractCode(addr, codeHash) +} + +func (f *ForkDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { + return f.active.ContractCodeSize(addr, codeHash) +} + +func (f *ForkDB) DiskDB() ethdb.KeyValueStore { + panic("DiskDB() during active Fork is not supported") +} + +func (f *ForkDB) PointCache() *utils.PointCache { + panic("PointCache() is not supported") +} + +func (f *ForkDB) TrieDB() *triedb.Database { + // The TrieDB is unused, but geth does use to check if Verkle is activated. + // So we have to create a read-only dummy one, to communicate that verkle really is disabled. + diskDB := rawdb.NewMemoryDatabase() + tdb := triedb.NewDatabase(diskDB, &triedb.Config{ + Preimages: false, + IsVerkle: false, + HashDB: nil, + PathDB: &pathdb.Config{ + StateHistory: 0, + CleanCacheSize: 0, + DirtyCacheSize: 0, + ReadOnly: true, + }, + }) + return tdb +} diff --git a/op-chain-ops/script/forking/diff.go b/op-chain-ops/script/forking/diff.go new file mode 100644 index 0000000000000..c46b2704dc85e --- /dev/null +++ b/op-chain-ops/script/forking/diff.go @@ -0,0 +1,88 @@ +package forking + +import ( + "bytes" + "maps" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" +) + +// AccountDiff represents changes to an account. Unchanged values of the account are not included. +type AccountDiff struct { + // Nonce change. + // No diff if nil. + Nonce *uint64 `json:"nonce"` + + // Balance change. + // No diff if nil. + Balance *uint256.Int `json:"balance"` + + // Storage changes. + // No diff if not present in map. Deletions are zero-value entries. + Storage map[common.Hash]common.Hash `json:"storage"` + + // CodeHash, for lookup of contract bytecode in the code diff map. + // No code-diff if nil. + CodeHash *common.Hash `json:"codeHash"` +} + +func (d *AccountDiff) Copy() *AccountDiff { + var out AccountDiff + if d.Nonce != nil { + v := *d.Nonce // copy the value + out.Nonce = &v + } + if d.Balance != nil { + out.Balance = d.Balance.Clone() + } + if d.Storage != nil { + out.Storage = maps.Clone(d.Storage) + } + if d.CodeHash != nil { + h := *d.CodeHash + out.CodeHash = &h + } + return &out +} + +type ExportDiff struct { + // Accounts diff. Deleted accounts are set to nil. + // Warning: this only contains finalized state changes. + // The state itself holds on to non-flushed changes. + Account map[common.Address]*AccountDiff `json:"account"` + + // Stores new contract codes by code-hash + Code map[common.Hash][]byte `json:"code"` +} + +func NewExportDiff() *ExportDiff { + return &ExportDiff{ + Account: make(map[common.Address]*AccountDiff), + Code: make(map[common.Hash][]byte), + } +} + +func (ed *ExportDiff) Copy() *ExportDiff { + out := &ExportDiff{ + Account: make(map[common.Address]*AccountDiff), + Code: make(map[common.Hash][]byte), + } + for addr, acc := range ed.Account { + out.Account[addr] = acc.Copy() + } + for addr, code := range ed.Code { + out.Code[addr] = bytes.Clone(code) + } + return out +} + +func (ed *ExportDiff) Any() bool { + return len(ed.Code) > 0 || len(ed.Account) > 0 +} + +func (ed *ExportDiff) Clear() { + ed.Account = make(map[common.Address]*AccountDiff) + ed.Code = make(map[common.Hash][]byte) +} diff --git a/op-chain-ops/script/forking/forking_test.go b/op-chain-ops/script/forking/forking_test.go new file mode 100644 index 0000000000000..e6e62c19fc19c --- /dev/null +++ b/op-chain-ops/script/forking/forking_test.go @@ -0,0 +1,291 @@ +package forking + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" +) + +type TestForkSource struct { + urlOrAlias string + stateRoot common.Hash + nonces map[common.Address]uint64 + balances map[common.Address]*uint256.Int + storage map[common.Address]map[common.Hash]common.Hash + code map[common.Address][]byte +} + +func (t TestForkSource) URLOrAlias() string { + return t.urlOrAlias +} + +func (t TestForkSource) StateRoot() common.Hash { + return t.stateRoot +} + +func (t TestForkSource) Nonce(addr common.Address) (uint64, error) { + return t.nonces[addr], nil +} + +func (t TestForkSource) Balance(addr common.Address) (*uint256.Int, error) { + b, ok := t.balances[addr] + if !ok { + return uint256.NewInt(0), nil + } + return b.Clone(), nil +} + +func (t TestForkSource) StorageAt(addr common.Address, key common.Hash) (common.Hash, error) { + storage, ok := t.storage[addr] + if !ok { + return common.Hash{}, nil + } + return storage[key], nil +} + +func (t TestForkSource) Code(addr common.Address) ([]byte, error) { + return t.code[addr], nil +} + +var _ ForkSource = (*TestForkSource)(nil) + +func TestForking(t *testing.T) { + // create regular DB + rawDB := rawdb.NewMemoryDatabase() + stateDB := state.NewDatabase(triedb.NewDatabase(rawDB, &triedb.Config{ + Preimages: true, // To be able to iterate the state we need the Preimages + IsVerkle: false, + HashDB: hashdb.Defaults, + PathDB: nil, + }), nil) + baseState, err := state.New(types.EmptyRootHash, stateDB) + if err != nil { + panic(fmt.Errorf("failed to create memory state db: %w", err)) + } + forkState := NewForkableState(baseState) + + // No active fork yet + id, active := forkState.ActiveFork() + require.False(t, active) + require.Equal(t, ForkID{}, id) + + name, err := forkState.ForkURLOrAlias(ForkID{}) + require.ErrorContains(t, err, "default") + require.Equal(t, "", name) + + alice := common.Address(bytes.Repeat([]byte{0xaa}, 20)) + bob := common.Address(bytes.Repeat([]byte{0xbb}, 20)) + + forkState.CreateAccount(alice) + forkState.SetNonce(alice, 3) + forkState.AddBalance(alice, uint256.NewInt(123), tracing.BalanceChangeUnspecified) + // Check if writes worked + require.Equal(t, uint64(123), forkState.GetBalance(alice).Uint64()) + require.Equal(t, uint64(3), forkState.GetNonce(alice)) + // No active fork yet, balance change should be applied to underlying base-state + require.Equal(t, uint64(123), baseState.GetBalance(alice).Uint64()) + require.Equal(t, uint64(3), baseState.GetNonce(alice)) + + src1 := &TestForkSource{ + urlOrAlias: "src 1", + stateRoot: crypto.Keccak256Hash([]byte("test fork state 1")), + nonces: map[common.Address]uint64{ + alice: uint64(42), + bob: uint64(1000), + }, + balances: make(map[common.Address]*uint256.Int), + storage: make(map[common.Address]map[common.Hash]common.Hash), + code: make(map[common.Address][]byte), + } + forkA, err := forkState.CreateSelectFork(src1) + require.NoError(t, err) + // Check that we selected A + id, active = forkState.ActiveFork() + require.True(t, active) + require.Equal(t, forkA, id) + name, err = forkState.ForkURLOrAlias(forkA) + require.NoError(t, err) + require.Equal(t, "src 1", name) + + // the fork has a different nonce for alice + require.Equal(t, uint64(42), forkState.GetNonce(alice)) + // the fork has Bob, which didn't exist thus far + require.Equal(t, uint64(1000), forkState.GetNonce(bob)) + + // Apply a diff change on top of the fork + forkState.SetNonce(bob, 99999) + + // Now unselect the fork, going back to the default again. + require.NoError(t, forkState.SelectFork(ForkID{})) + // No longer active fork + id, active = forkState.ActiveFork() + require.False(t, active) + require.Equal(t, ForkID{}, id) + + // Check that things are back to normal + require.Equal(t, uint64(3), forkState.GetNonce(alice)) + require.Equal(t, uint64(0), forkState.GetNonce(bob)) + + // Make a change to the base-state, to see if it survives going back to the fork. + forkState.SetNonce(bob, 5) + + // Re-select the fork, see if the changes come back, including the diff we made + require.NoError(t, forkState.SelectFork(forkA)) + require.Equal(t, uint64(42), forkState.GetNonce(alice)) + require.Equal(t, uint64(99999), forkState.GetNonce(bob)) + + // This change will continue to be visible across forks, + // alice is going to be persistent. + forkState.SetNonce(alice, 777) + + // Now make Alice persistent, see if we can get the original value + forkState.MakePersistent(alice) + + // Activate a fork, to see if alice is really persistent + src2 := &TestForkSource{ + urlOrAlias: "src 2", + stateRoot: crypto.Keccak256Hash([]byte("test fork state 2")), + nonces: map[common.Address]uint64{ + alice: uint64(2222), + bob: uint64(222), + }, + balances: make(map[common.Address]*uint256.Int), + storage: make(map[common.Address]map[common.Hash]common.Hash), + code: make(map[common.Address][]byte), + } + tmpFork, err := forkState.CreateSelectFork(src2) + require.NoError(t, err) + require.Equal(t, uint64(777), forkState.GetNonce(alice), "persistent original value") + // While bob is still read from the fork + require.Equal(t, uint64(222), forkState.GetNonce(bob), "bob is forked") + + // Mutate both, and undo the fork, to test if the persistent change is still there in non-fork mode + forkState.SetNonce(alice, 1001) // this mutates forkA, because alice was made persistent there + forkState.SetNonce(bob, 1002) + require.NoError(t, forkState.SelectFork(ForkID{})) + require.Equal(t, uint64(1001), forkState.GetNonce(alice), "alice is persistent") + require.Equal(t, uint64(5), forkState.GetNonce(bob), "bob is not persistent") + + // Stop alice persistence. Forks can now override it again. + forkState.RevokePersistent(alice) + // This foundry behavior is unspecified/undocumented. + // Not sure if correctly doing it by dropping the previously persisted state if it comes from another fork. + require.Equal(t, uint64(3), forkState.GetNonce(alice)) + require.Equal(t, uint64(3), baseState.GetNonce(alice)) + require.Equal(t, uint64(5), forkState.GetNonce(bob)) + + // Create another fork, don't select it immediately + src3 := &TestForkSource{ + urlOrAlias: "src 3", + stateRoot: crypto.Keccak256Hash([]byte("test fork state 3")), + nonces: map[common.Address]uint64{ + alice: uint64(3333), + }, + balances: make(map[common.Address]*uint256.Int), + storage: make(map[common.Address]map[common.Hash]common.Hash), + code: make(map[common.Address][]byte), + } + forkB, err := forkState.CreateFork(src3) + require.NoError(t, err) + + id, active = forkState.ActiveFork() + require.False(t, active) + require.Equal(t, ForkID{}, id) + + // forkA is still bound to src 1 + name, err = forkState.ForkURLOrAlias(forkA) + require.NoError(t, err) + require.Equal(t, "src 1", name) + // tmpFork is still bound to src 2 + name, err = forkState.ForkURLOrAlias(tmpFork) + require.NoError(t, err) + require.Equal(t, "src 2", name) + // forkB is on src 3 + name, err = forkState.ForkURLOrAlias(forkB) + require.NoError(t, err) + require.Equal(t, "src 3", name) + + require.Equal(t, uint64(3), forkState.GetNonce(alice), "not forked yet") + require.NoError(t, forkState.SelectFork(forkB)) + id, active = forkState.ActiveFork() + require.True(t, active) + require.Equal(t, forkB, id) + + // check if successfully forked now + require.Equal(t, uint64(3333), forkState.GetNonce(alice), "fork B active now") + // Bob is not in this fork. But that doesn't mean the base-state should be used. + require.Equal(t, uint64(0), forkState.GetNonce(bob)) + + // See if we can go from B straight to A + require.NoError(t, forkState.SelectFork(forkA)) + require.Equal(t, uint64(1001), forkState.GetNonce(alice), "alice from A says hi") + // And back to B + require.NoError(t, forkState.SelectFork(forkB)) + require.Equal(t, uint64(3333), forkState.GetNonce(alice), "alice from B says hi") + + // And a fork on top of a fork; forks don't stack, they are their own individual contexts. + src4 := &TestForkSource{ + urlOrAlias: "src 4", + stateRoot: crypto.Keccak256Hash([]byte("test fork state 4")), + nonces: map[common.Address]uint64{ + bob: uint64(9000), + }, + balances: make(map[common.Address]*uint256.Int), + storage: make(map[common.Address]map[common.Hash]common.Hash), + code: make(map[common.Address][]byte), + } + forkC, err := forkState.CreateSelectFork(src4) + require.NoError(t, err) + // No alice in this fork. + require.Equal(t, uint64(0), forkState.GetNonce(alice)) + // But bob is set + require.Equal(t, uint64(9000), forkState.GetNonce(bob)) + + // Put in some mutations, for the fork-diff testing + forkState.SetNonce(alice, 1234) + forkState.SetBalance(alice, uint256.NewInt(100_000), tracing.BalanceChangeUnspecified) + forkState.SetState(alice, common.Hash{4}, common.Hash{42}) + forkState.SetState(alice, common.Hash{5}, common.Hash{100}) + forkState.SetCode(alice, []byte("hello world")) + + // Check the name + name, err = forkState.ForkURLOrAlias(forkC) + require.NoError(t, err) + require.Equal(t, "src 4", name) + + // Now test our fork-diff exporting: + // it needs to reflect the changes we made to the fork, but not other fork contents. + forkADiff, err := forkState.ExportDiff(forkA) + require.NoError(t, err) + require.NotNil(t, forkADiff.Account[alice]) + require.Equal(t, uint64(1001), *forkADiff.Account[alice].Nonce) + require.Equal(t, uint64(99999), *forkADiff.Account[bob].Nonce) + + forkBDiff, err := forkState.ExportDiff(forkB) + require.NoError(t, err) + require.Len(t, forkBDiff.Account, 0, "no changes to fork B") + + forkCDiff, err := forkState.ExportDiff(forkC) + require.NoError(t, err) + require.Contains(t, forkCDiff.Account, alice) + require.NotContains(t, forkCDiff.Account, bob) + require.Equal(t, uint64(1234), *forkCDiff.Account[alice].Nonce) + require.Equal(t, uint64(100_000), forkCDiff.Account[alice].Balance.Uint64()) + require.Equal(t, common.Hash{42}, forkCDiff.Account[alice].Storage[common.Hash{4}]) + require.Equal(t, common.Hash{100}, forkCDiff.Account[alice].Storage[common.Hash{5}]) + require.Equal(t, crypto.Keccak256Hash([]byte("hello world")), *forkCDiff.Account[alice].CodeHash) + require.Equal(t, []byte("hello world"), forkCDiff.Code[*forkCDiff.Account[alice].CodeHash]) +} diff --git a/op-chain-ops/script/forking/iface.go b/op-chain-ops/script/forking/iface.go new file mode 100644 index 0000000000000..2e1c29031be17 --- /dev/null +++ b/op-chain-ops/script/forking/iface.go @@ -0,0 +1,52 @@ +package forking + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +type VMStateDB interface { + vm.StateDB + Finalise(deleteEmptyObjects bool) + // SetBalance sets the balance of an account. Not part of the geth VM StateDB interface (add/sub balance are). + SetBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) +} + +// ForkID is an identifier of a fork +type ForkID uint256.Int + +func ForkIDFromBig(b *big.Int) ForkID { + return ForkID(*uint256.MustFromBig(b)) +} + +// U256 returns a uint256 copy of the fork ID, for usage inside the EVM. +func (id *ForkID) U256() *uint256.Int { + return new(uint256.Int).Set((*uint256.Int)(id)) +} + +func (id ForkID) String() string { + return (*uint256.Int)(&id).String() +} + +// ForkSource is a read-only source for ethereum state, +// that can be used to fork a ForkableState. +type ForkSource interface { + // URLOrAlias returns the URL or alias that the fork uses. This is not unique to a single fork. + URLOrAlias() string + // StateRoot returns the accounts-trie root of the committed-to state. + // This root must never change. + StateRoot() common.Hash + // Nonce returns 0, without error, if the account does not exist. + Nonce(addr common.Address) (uint64, error) + // Balance returns 0, without error, if the account does not exist. + Balance(addr common.Address) (*uint256.Int, error) + // StorageAt returns a zeroed hash, without error, if the storage does not exist. + StorageAt(addr common.Address, key common.Hash) (common.Hash, error) + // Code returns an empty byte slice, without error, if no code exists. + Code(addr common.Address) ([]byte, error) +} diff --git a/op-chain-ops/script/forking/reader.go b/op-chain-ops/script/forking/reader.go new file mode 100644 index 0000000000000..d943ddf14449a --- /dev/null +++ b/op-chain-ops/script/forking/reader.go @@ -0,0 +1,36 @@ +package forking + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" +) + +// forkStateReader implements the state.Reader abstraction, +// for read-only access to a state-trie at a particular state-root. +type forkStateReader struct { + trie *ForkedAccountsTrie +} + +var _ state.Reader = (*forkStateReader)(nil) + +func (f *forkStateReader) Account(addr common.Address) (*types.StateAccount, error) { + acc, err := f.trie.GetAccount(addr) + if err != nil { + return nil, err + } + // We copy because the Reader interface defines that it should be safe to modify after returning. + return acc.Copy(), nil +} + +func (f *forkStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { + v, err := f.trie.GetStorage(addr, slot[:]) + if err != nil { + return common.Hash{}, err + } + return common.Hash(v), nil +} + +func (f *forkStateReader) Copy() state.Reader { + return f +} diff --git a/op-chain-ops/script/forking/rpc.go b/op-chain-ops/script/forking/rpc.go new file mode 100644 index 0000000000000..501c0fd6e372d --- /dev/null +++ b/op-chain-ops/script/forking/rpc.go @@ -0,0 +1,140 @@ +package forking + +import ( + "context" + "fmt" + "time" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/op-service/retry" +) + +type RPCClient interface { + CallContext(ctx context.Context, result any, method string, args ...any) error +} + +type RPCSource struct { + stateRoot common.Hash + blockHash common.Hash + + maxAttempts int + timeout time.Duration + strategy retry.Strategy + + ctx context.Context + cancel context.CancelFunc + + client RPCClient + urlOrAlias string +} + +var _ ForkSource = (*RPCSource)(nil) + +func RPCSourceByNumber(urlOrAlias string, cl RPCClient, num uint64) (*RPCSource, error) { + src := newRPCSource(urlOrAlias, cl) + err := src.init(hexutil.Uint64(num)) + return src, err +} + +func RPCSourceByHash(urlOrAlias string, cl RPCClient, h common.Hash) (*RPCSource, error) { + src := newRPCSource(urlOrAlias, cl) + err := src.init(h) + return src, err +} + +func newRPCSource(urlOrAlias string, cl RPCClient) *RPCSource { + ctx, cancel := context.WithCancel(context.Background()) + return &RPCSource{ + maxAttempts: 10, + timeout: time.Second * 10, + strategy: retry.Exponential(), + ctx: ctx, + cancel: cancel, + client: cl, + urlOrAlias: urlOrAlias, + } +} + +type Header struct { + StateRoot common.Hash `json:"stateRoot"` + BlockHash common.Hash `json:"hash"` +} + +func (r *RPCSource) init(id any) error { + head, err := retry.Do[*Header](r.ctx, r.maxAttempts, r.strategy, func() (*Header, error) { + var result *Header + err := r.client.CallContext(r.ctx, &result, "eth_getBlockByNumber", id, false) + if err == nil && result == nil { + err = ethereum.NotFound + } + return result, err + }) + if err != nil { + return fmt.Errorf("failed to initialize RPC fork source around block %v: %w", id, err) + } + r.blockHash = head.BlockHash + r.stateRoot = head.StateRoot + return nil +} + +func (c *RPCSource) URLOrAlias() string { + return c.urlOrAlias +} + +func (r *RPCSource) BlockHash() common.Hash { + return r.blockHash +} + +func (r *RPCSource) StateRoot() common.Hash { + return r.stateRoot +} + +func (r *RPCSource) Nonce(addr common.Address) (uint64, error) { + return retry.Do[uint64](r.ctx, r.maxAttempts, r.strategy, func() (uint64, error) { + ctx, cancel := context.WithTimeout(r.ctx, r.timeout) + defer cancel() + var result hexutil.Uint64 + err := r.client.CallContext(ctx, &result, "eth_getTransactionCount", addr, r.blockHash) + return uint64(result), err + }) +} + +func (r *RPCSource) Balance(addr common.Address) (*uint256.Int, error) { + return retry.Do[*uint256.Int](r.ctx, r.maxAttempts, r.strategy, func() (*uint256.Int, error) { + ctx, cancel := context.WithTimeout(r.ctx, r.timeout) + defer cancel() + var result hexutil.U256 + err := r.client.CallContext(ctx, &result, "eth_getBalance", addr, r.blockHash) + return (*uint256.Int)(&result), err + }) +} + +func (r *RPCSource) StorageAt(addr common.Address, key common.Hash) (common.Hash, error) { + return retry.Do[common.Hash](r.ctx, r.maxAttempts, r.strategy, func() (common.Hash, error) { + ctx, cancel := context.WithTimeout(r.ctx, r.timeout) + defer cancel() + var result common.Hash + err := r.client.CallContext(ctx, &result, "eth_getStorageAt", addr, key, r.blockHash) + return result, err + }) +} + +func (r *RPCSource) Code(addr common.Address) ([]byte, error) { + return retry.Do[[]byte](r.ctx, r.maxAttempts, r.strategy, func() ([]byte, error) { + ctx, cancel := context.WithTimeout(r.ctx, r.timeout) + defer cancel() + var result hexutil.Bytes + err := r.client.CallContext(ctx, &result, "eth_getCode", addr, r.blockHash) + return result, err + }) +} + +// Close stops any ongoing RPC requests by cancelling the RPC context +func (r *RPCSource) Close() { + r.cancel() +} diff --git a/op-chain-ops/script/forking/rpc_test.go b/op-chain-ops/script/forking/rpc_test.go new file mode 100644 index 0000000000000..9f23e957b1e99 --- /dev/null +++ b/op-chain-ops/script/forking/rpc_test.go @@ -0,0 +1,210 @@ +package forking + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-service/retry" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/holiman/uint256" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// MockRPCClient implements RPCClient interface for testing +type MockRPCClient struct { + mock.Mock +} + +func (m *MockRPCClient) CallContext(ctx context.Context, result any, method string, args ...any) error { + return m.Called(ctx, result, method, args).Error(0) +} + +func TestRPCSourceInitialization(t *testing.T) { + mockClient := new(MockRPCClient) + expectedStateRoot := common.HexToHash("0x1234") + expectedBlockHash := common.HexToHash("0x5678") + + t.Run("initialization by block number", func(t *testing.T) { + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("**forking.Header"), + "eth_getBlockByNumber", []any{hexutil.Uint64(123), false}). + Run(func(args mock.Arguments) { + result := args.Get(1).(**Header) + *result = &Header{ + StateRoot: expectedStateRoot, + BlockHash: expectedBlockHash, + } + }). + Return(nil).Once() + + source, err := RPCSourceByNumber("test_url", mockClient, 123) + require.NoError(t, err) + require.Equal(t, expectedStateRoot, source.StateRoot()) + require.Equal(t, expectedBlockHash, source.BlockHash()) + }) + + t.Run("initialization by block hash", func(t *testing.T) { + blockHash := common.HexToHash("0xabcd") + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("**forking.Header"), + "eth_getBlockByNumber", []any{blockHash, false}). + Run(func(args mock.Arguments) { + result := args.Get(1).(**Header) + *result = &Header{ + StateRoot: expectedStateRoot, + BlockHash: expectedBlockHash, + } + }). + Return(nil).Once() + + source, err := RPCSourceByHash("test_url", mockClient, blockHash) + require.NoError(t, err) + require.Equal(t, expectedStateRoot, source.StateRoot()) + require.Equal(t, expectedBlockHash, source.BlockHash()) + }) + + t.Run("initialization failure", func(t *testing.T) { + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("**forking.Header"), + "eth_getBlockByNumber", []any{hexutil.Uint64(999), false}). + Return(ethereum.NotFound).Times(2) + + src := newRPCSource("test_url", mockClient) + strategy := retry.Exponential() + strategy.(*retry.ExponentialStrategy).Max = 100 * time.Millisecond + src.strategy = strategy + src.maxAttempts = 2 + require.Error(t, src.init(hexutil.Uint64(999))) + }) +} + +func TestRPCSourceDataRetrieval(t *testing.T) { + mockClient := new(MockRPCClient) + testAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") + blockHash := common.HexToHash("0xabcd") + + source := &RPCSource{ + blockHash: blockHash, + client: mockClient, + ctx: context.Background(), + strategy: retry.Exponential(), + maxAttempts: 10, + timeout: time.Second * 10, + } + + t.Run("get nonce", func(t *testing.T) { + expectedNonce := uint64(5) + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Uint64"), + "eth_getTransactionCount", []any{testAddr, blockHash}). + Run(func(args mock.Arguments) { + result := args.Get(1).(*hexutil.Uint64) + *result = hexutil.Uint64(expectedNonce) + }). + Return(nil).Once() + + nonce, err := source.Nonce(testAddr) + require.NoError(t, err) + require.Equal(t, expectedNonce, nonce) + }) + + t.Run("get balance", func(t *testing.T) { + expectedBalance := uint256.NewInt(1000) + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.U256"), + "eth_getBalance", []any{testAddr, blockHash}). + Run(func(args mock.Arguments) { + result := args.Get(1).(*hexutil.U256) + *(*uint256.Int)(result) = *expectedBalance + }). + Return(nil).Once() + + balance, err := source.Balance(testAddr) + require.NoError(t, err) + require.Equal(t, expectedBalance, balance) + }) + + t.Run("get storage", func(t *testing.T) { + storageKey := common.HexToHash("0x1234") + expectedValue := common.HexToHash("0x5678") + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("*common.Hash"), + "eth_getStorageAt", []any{testAddr, storageKey, blockHash}). + Run(func(args mock.Arguments) { + result := args.Get(1).(*common.Hash) + *result = expectedValue + }). + Return(nil).Once() + + value, err := source.StorageAt(testAddr, storageKey) + require.NoError(t, err) + require.Equal(t, expectedValue, value) + }) + + t.Run("get code", func(t *testing.T) { + expectedCode := []byte{1, 2, 3, 4} + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), + "eth_getCode", []any{testAddr, blockHash}). + Run(func(args mock.Arguments) { + result := args.Get(1).(*hexutil.Bytes) + *result = expectedCode + }). + Return(nil).Once() + + code, err := source.Code(testAddr) + require.NoError(t, err) + require.Equal(t, expectedCode, code) + }) +} + +func TestRPCSourceRetry(t *testing.T) { + mockClient := new(MockRPCClient) + testAddr := common.HexToAddress("0x1234") + blockHash := common.HexToHash("0xabcd") + strategy := retry.Exponential() + strategy.(*retry.ExponentialStrategy).Max = 100 * time.Millisecond + + source := &RPCSource{ + blockHash: blockHash, + client: mockClient, + ctx: context.Background(), + strategy: strategy, + maxAttempts: 3, + timeout: time.Second * 10, + } + + t.Run("retry on temporary error", func(t *testing.T) { + tempError := errors.New("temporary network error") + + // Fail twice, succeed on third attempt + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Uint64"), + "eth_getTransactionCount", []any{testAddr, blockHash}). + Return(tempError).Times(2) + + mockClient.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Uint64"), + "eth_getTransactionCount", []any{testAddr, blockHash}). + Run(func(args mock.Arguments) { + result := args.Get(1).(*hexutil.Uint64) + *result = hexutil.Uint64(5) + }). + Return(nil).Once() + + nonce, err := source.Nonce(testAddr) + require.NoError(t, err) + require.Equal(t, uint64(5), nonce) + }) +} + +func TestRPCSourceClose(t *testing.T) { + mockClient := new(MockRPCClient) + source := newRPCSource("test_url", mockClient) + + // Verify context is active before close + require.NoError(t, source.ctx.Err()) + + source.Close() + + // Verify context is cancelled after close + require.Error(t, source.ctx.Err()) + require.Equal(t, context.Canceled, source.ctx.Err()) +} diff --git a/op-chain-ops/script/forking/state.go b/op-chain-ops/script/forking/state.go new file mode 100644 index 0000000000000..c3b6da1e32366 --- /dev/null +++ b/op-chain-ops/script/forking/state.go @@ -0,0 +1,391 @@ +package forking + +import ( + "errors" + "fmt" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/holiman/uint256" +) + +type forkStateEntry struct { + state *state.StateDB +} + +func (fe *forkStateEntry) DB() *ForkDB { + return fe.state.Database().(*ForkDB) +} + +// ForkableState implements the vm.StateDB interface, +// and a few other methods as defined in the VMStateDB interface. +// This state can be forked in-place, +// swapping over operations to route to in-memory states that wrap fork sources. +type ForkableState struct { + selected VMStateDB + + activeFork ForkID + forks map[ForkID]*forkStateEntry + + // persistent accounts will override any interactions + // to be directly with the forkID that was active at the time it was made persistent, + // rather than whatever fork is currently active. + persistent map[common.Address]ForkID + + fallback VMStateDB + + idCounter uint64 +} + +var _ VMStateDB = (*ForkableState)(nil) + +func NewForkableState(base VMStateDB) *ForkableState { + return &ForkableState{ + selected: base, + activeFork: ForkID{}, + forks: make(map[ForkID]*forkStateEntry), + persistent: map[common.Address]ForkID{ + addresses.DefaultSenderAddr: ForkID{}, + addresses.VMAddr: ForkID{}, + addresses.ConsoleAddr: ForkID{}, + }, + fallback: base, + idCounter: 0, + } +} + +// ExportDiff exports a state diff. Warning: diffs are like flushed states. +// So we flush the state, making all the contents cold, losing transient storage, etc. +func (fst *ForkableState) ExportDiff(id ForkID) (*ExportDiff, error) { + if id == (ForkID{}) { + return nil, errors.New("default no-fork state does not have an exportable diff") + } + f, ok := fst.forks[id] + if !ok { + return nil, fmt.Errorf("unknown fork %q", id) + } + // Finalize the state content, so we can get an accurate diff. + f.state.IntermediateRoot(true) + tr := f.state.GetTrie() + ft, ok := tr.(*ForkedAccountsTrie) + if !ok { + return nil, fmt.Errorf("forked state trie is unexpectedly not a ForkedAccountsTrie: %T", tr) + } + diff := ft.ExportDiff() + // Now re-init the state, so we can use it again (albeit it cold). + forkDB := &ForkDB{active: ft} + st, err := state.New(forkDB.active.stateRoot, forkDB) + if err != nil { + return nil, fmt.Errorf("failed to construct fork state: %w", err) + } + fst.forks[id].state = st + if fst.activeFork == id { + fst.selected = st + } + return diff, nil +} + +// CreateSelectFork is like vm.createSelectFork, it creates a fork, and selects it immediately. +func (fst *ForkableState) CreateSelectFork(source ForkSource) (ForkID, error) { + id, err := fst.CreateFork(source) + if err != nil { + return id, err + } + return id, fst.SelectFork(id) +} + +// CreateFork is like vm.createFork, it creates a fork, but does not select it yet. +func (fst *ForkableState) CreateFork(source ForkSource) (ForkID, error) { + fst.idCounter += 1 // increment first, don't use ID 0 + id := ForkID(*uint256.NewInt(fst.idCounter)) + _, ok := fst.forks[id] + if ok { // sanity check our ID counter is consistent with the tracked forks + return id, fmt.Errorf("cannot create fork, fork %q already exists", id) + } + forkDB := NewForkDB(source) + st, err := state.New(forkDB.active.stateRoot, forkDB) + if err != nil { + return id, fmt.Errorf("failed to construct fork state: %w", err) + } + fst.forks[id] = &forkStateEntry{ + state: st, + } + return id, nil +} + +// SelectFork is like vm.selectFork, it activates the usage of a previously created fork. +func (fst *ForkableState) SelectFork(id ForkID) error { + if id == (ForkID{}) { + fst.selected = fst.fallback + fst.activeFork = ForkID{} + return nil + } + f, ok := fst.forks[id] + if !ok { + return fmt.Errorf("cannot select fork, fork %q is unknown", id) + } + fst.selected = f.state + fst.activeFork = id + return nil +} + +// ResetFork resets the fork to be coupled to the given fork-source. +// Any ephemeral state changes (transient storage, warm s-loads, etc.) +// as well as any uncommitted state, as well as any previously flushed diffs, will be lost. +func (fst *ForkableState) ResetFork(id ForkID, src ForkSource) error { + if id == (ForkID{}) { + return errors.New("default no-fork state cannot change its ForkSource") + } + f, ok := fst.forks[id] + if !ok { + return fmt.Errorf("unknown fork %q", id) + } + // Now create a new state + forkDB := NewForkDB(src) + st, err := state.New(src.StateRoot(), forkDB) + if err != nil { + return fmt.Errorf("failed to construct fork state: %w", err) + } + f.state = st + if fst.activeFork == id { + fst.selected = st + } + return nil +} + +// ActiveFork returns the ID current active fork, or active == false if no fork is active. +func (fst *ForkableState) ActiveFork() (id ForkID, active bool) { + return fst.activeFork, fst.activeFork != (ForkID{}) +} + +// ForkURLOrAlias returns the URL or alias that the fork was configured with as source. +// Returns an error if no fork is active +func (fst *ForkableState) ForkURLOrAlias(id ForkID) (string, error) { + if id == (ForkID{}) { + return "", errors.New("default no-fork state does not have an URL or Alias") + } + f, ok := fst.forks[id] + if !ok { + return "", fmt.Errorf("unknown fork %q", id) + } + return f.DB().active.src.URLOrAlias(), nil +} + +// SubstituteBaseState substitutes in a fallback state. +func (fst *ForkableState) SubstituteBaseState(base VMStateDB) { + fst.fallback = base + // If the fallback is currently selected, also updated the fallback. + if fst.activeFork == (ForkID{}) { + fst.selected = base + } +} + +// MakePersistent is like vm.makePersistent, it maintains this account context across all forks. +// It does not make the account of a fork persistent, it makes an account override what might be in a fork. +func (fst *ForkableState) MakePersistent(addr common.Address) { + fst.persistent[addr] = fst.activeFork +} + +// MakeExcluded excludes an account from forking. This is useful for things like scripts, which +// should always use the fallback state. +func (fst *ForkableState) MakeExcluded(addr common.Address) { + fst.persistent[addr] = ForkID{} +} + +// RevokePersistent is like vm.revokePersistent, it undoes a previous vm.makePersistent. +func (fst *ForkableState) RevokePersistent(addr common.Address) { + delete(fst.persistent, addr) +} + +// RevokeExcluded undoes MakeExcluded. It will panic if the account was marked as +// persistent in a different fork. +func (fst *ForkableState) RevokeExcluded(addr common.Address) { + forkID, ok := fst.persistent[addr] + if ok && forkID != (ForkID{}) { + panic(fmt.Sprintf("cannot revoke excluded account %s since it was made persistent in fork %q", addr, forkID)) + } + delete(fst.persistent, addr) +} + +// IsPersistent is like vm.isPersistent, it checks if an account persists across forks. +func (fst *ForkableState) IsPersistent(addr common.Address) bool { + _, ok := fst.persistent[addr] + return ok +} + +func (fst *ForkableState) stateFor(addr common.Address) VMStateDB { + // if forked, check if we persisted this account across forks + persistedForkID, ok := fst.persistent[addr] + if ok { + if persistedForkID == (ForkID{}) { + return fst.fallback + } + return fst.forks[persistedForkID].state + } + // This may be the fallback state, if no fork is active. + return fst.selected +} + +// Finalise finalises the state by removing the destructed objects and clears +// the journal as well as the refunds. Finalise, however, will not push any updates +// into the tries just yet. +// +// The changes will be flushed to the underlying DB. +// A *ForkDB if the state is currently forked. +func (fst *ForkableState) Finalise(deleteEmptyObjects bool) { + fst.selected.Finalise(deleteEmptyObjects) +} + +func (fst *ForkableState) CreateAccount(address common.Address) { + fst.stateFor(address).CreateAccount(address) +} + +func (fst *ForkableState) CreateContract(address common.Address) { + fst.stateFor(address).CreateContract(address) +} + +func (fst *ForkableState) SubBalance(address common.Address, u *uint256.Int, reason tracing.BalanceChangeReason) { + fst.stateFor(address).SubBalance(address, u, reason) +} + +func (fst *ForkableState) AddBalance(address common.Address, u *uint256.Int, reason tracing.BalanceChangeReason) { + fst.stateFor(address).AddBalance(address, u, reason) +} + +func (fst *ForkableState) GetBalance(address common.Address) *uint256.Int { + return fst.stateFor(address).GetBalance(address) +} + +func (fst *ForkableState) GetNonce(address common.Address) uint64 { + return fst.stateFor(address).GetNonce(address) +} + +func (fst *ForkableState) SetNonce(address common.Address, u uint64) { + fst.stateFor(address).SetNonce(address, u) +} + +func (fst *ForkableState) GetCodeHash(address common.Address) common.Hash { + return fst.stateFor(address).GetCodeHash(address) +} + +func (fst *ForkableState) GetCode(address common.Address) []byte { + return fst.stateFor(address).GetCode(address) +} + +func (fst *ForkableState) SetCode(address common.Address, bytes []byte) { + fst.stateFor(address).SetCode(address, bytes) +} + +func (fst *ForkableState) GetCodeSize(address common.Address) int { + return fst.stateFor(address).GetCodeSize(address) +} + +func (fst *ForkableState) AddRefund(u uint64) { + fst.selected.AddRefund(u) +} + +func (fst *ForkableState) SubRefund(u uint64) { + fst.selected.SubRefund(u) +} + +func (fst *ForkableState) GetRefund() uint64 { + return fst.selected.GetRefund() +} + +func (fst *ForkableState) GetCommittedState(address common.Address, hash common.Hash) common.Hash { + return fst.stateFor(address).GetCommittedState(address, hash) +} + +func (fst *ForkableState) GetState(address common.Address, k common.Hash) common.Hash { + return fst.stateFor(address).GetState(address, k) +} + +func (fst *ForkableState) SetState(address common.Address, k common.Hash, v common.Hash) { + fst.stateFor(address).SetState(address, k, v) +} + +func (fst *ForkableState) GetStorageRoot(addr common.Address) common.Hash { + return fst.stateFor(addr).GetStorageRoot(addr) +} + +func (fst *ForkableState) GetTransientState(addr common.Address, key common.Hash) common.Hash { + return fst.stateFor(addr).GetTransientState(addr, key) +} + +func (fst *ForkableState) SetTransientState(addr common.Address, key, value common.Hash) { + fst.stateFor(addr).SetTransientState(addr, key, value) +} + +func (fst *ForkableState) SelfDestruct(address common.Address) { + fst.stateFor(address).SelfDestruct(address) +} + +func (fst *ForkableState) HasSelfDestructed(address common.Address) bool { + return fst.stateFor(address).HasSelfDestructed(address) +} + +func (fst *ForkableState) Selfdestruct6780(address common.Address) { + fst.stateFor(address).Selfdestruct6780(address) +} + +func (fst *ForkableState) Exist(address common.Address) bool { + return fst.stateFor(address).Exist(address) +} + +func (fst *ForkableState) Empty(address common.Address) bool { + return fst.stateFor(address).Empty(address) +} + +func (fst *ForkableState) AddressInAccessList(addr common.Address) bool { + return fst.stateFor(addr).AddressInAccessList(addr) +} + +func (fst *ForkableState) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + return fst.stateFor(addr).SlotInAccessList(addr, slot) +} + +func (fst *ForkableState) AddAddressToAccessList(addr common.Address) { + fst.stateFor(addr).AddAddressToAccessList(addr) +} + +func (fst *ForkableState) AddSlotToAccessList(addr common.Address, slot common.Hash) { + fst.stateFor(addr).AddSlotToAccessList(addr, slot) +} + +func (fst *ForkableState) PointCache() *utils.PointCache { + return fst.selected.PointCache() +} + +func (fst *ForkableState) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) { + fst.selected.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses) +} + +func (fst *ForkableState) RevertToSnapshot(i int) { + fst.selected.RevertToSnapshot(i) +} + +func (fst *ForkableState) Snapshot() int { + return fst.selected.Snapshot() +} + +func (fst *ForkableState) AddLog(log *types.Log) { + fst.selected.AddLog(log) +} + +func (fst *ForkableState) AddPreimage(hash common.Hash, img []byte) { + fst.selected.AddPreimage(hash, img) +} + +func (fst *ForkableState) Witness() *stateless.Witness { + return fst.selected.Witness() +} + +func (fst *ForkableState) SetBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { + fst.stateFor(addr).SetBalance(addr, amount, reason) +} diff --git a/op-chain-ops/script/forking/trie.go b/op-chain-ops/script/forking/trie.go new file mode 100644 index 0000000000000..3af50bad973bd --- /dev/null +++ b/op-chain-ops/script/forking/trie.go @@ -0,0 +1,223 @@ +package forking + +import ( + "errors" + "fmt" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +type ForkedAccountsTrie struct { + // stateRoot that this diff is based on top of + stateRoot common.Hash + + // source to retrieve data from when it's not in the diff + src ForkSource + + diff *ExportDiff +} + +var _ state.Trie = (*ForkedAccountsTrie)(nil) + +func (f *ForkedAccountsTrie) Copy() *ForkedAccountsTrie { + return &ForkedAccountsTrie{ + stateRoot: f.stateRoot, + diff: f.diff.Copy(), + } +} + +func (f *ForkedAccountsTrie) ExportDiff() *ExportDiff { + return f.diff.Copy() +} + +func (f *ForkedAccountsTrie) HasDiff() bool { + return len(f.diff.Code) > 0 || len(f.diff.Account) > 0 +} + +// ClearDiff clears the flushed changes. This does not clear the warm state changes. +// To fully clear, first Finalise the forked state that uses this trie, and then clear the diff. +func (f *ForkedAccountsTrie) ClearDiff() { + f.diff.Clear() +} + +// ContractCode is not directly part of the vm.State interface, +// but is used by the ForkDB to retrieve the contract code. +func (f *ForkedAccountsTrie) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) { + diffAcc, ok := f.diff.Account[addr] + if ok { + if diffAcc.CodeHash != nil && *diffAcc.CodeHash != codeHash { + return nil, fmt.Errorf("account code changed to %s, cannot get code %s of account %s", *diffAcc.CodeHash, codeHash, addr) + } + if code, ok := f.diff.Code[codeHash]; ok { + return code, nil + } + // if not in codeDiff, the actual code has not changed. + } + code, err := f.src.Code(addr) + if err != nil { + return nil, fmt.Errorf("failed to retrieve code: %w", err) + } + // sanity-check the retrieved code matches the expected codehash + if h := crypto.Keccak256Hash(code); h != codeHash { + return nil, fmt.Errorf("retrieved code of %s hashed to %s, but expected %s", addr, h, codeHash) + } + return code, nil +} + +// ContractCodeSize is not directly part of the vm.State interface, +// but is used by the ForkDB to retrieve the contract code-size. +func (f *ForkedAccountsTrie) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { + code, err := f.ContractCode(addr, codeHash) + if err != nil { + return 0, fmt.Errorf("cannot get contract code to determine code size: %w", err) + } + return len(code), nil +} + +func (f *ForkedAccountsTrie) GetKey(bytes []byte) []byte { + panic("arbitrary key lookups on ForkedAccountsTrie are not supported") +} + +func (f *ForkedAccountsTrie) GetAccount(address common.Address) (*types.StateAccount, error) { + acc := &types.StateAccount{ + Nonce: 0, + Balance: nil, + Root: fakeRoot, + CodeHash: nil, + } + diffAcc := f.diff.Account[address] + if diffAcc != nil && diffAcc.Nonce != nil { + acc.Nonce = *diffAcc.Nonce + } else { + v, err := f.src.Nonce(address) + if err != nil { + return nil, fmt.Errorf("failed to retrieve nonce of account %s: %w", address, err) + } + acc.Nonce = v + } + if diffAcc != nil && diffAcc.Balance != nil { + acc.Balance = new(uint256.Int).Set(diffAcc.Balance) + } else { + v, err := f.src.Balance(address) + if err != nil { + return nil, fmt.Errorf("failed to retrieve balance of account %s: %w", address, err) + } + acc.Balance = new(uint256.Int).Set(v) + } + if diffAcc != nil && diffAcc.CodeHash != nil { + cpy := *diffAcc.CodeHash + acc.CodeHash = cpy.Bytes() + } else { + v, err := f.src.Code(address) + if err != nil { + return nil, fmt.Errorf("failed to retrieve code of account %s: %w", address, err) + } + acc.CodeHash = crypto.Keccak256Hash(v).Bytes() + } + return acc, nil +} + +func (f *ForkedAccountsTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { + k := common.BytesToHash(key) + diffAcc, ok := f.diff.Account[addr] + if ok { // if there is a diff, try and see if it contains a storage diff + v, ok := diffAcc.Storage[k] + if ok { // if the storage has changed, return that change + return v.Bytes(), nil + } + } + v, err := f.src.StorageAt(addr, k) + if err != nil { + return nil, err + } + return v.Bytes(), nil +} + +func (f *ForkedAccountsTrie) UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error { + // Ignored, account contains the code details we need. + // Also see the trie.StateTrie of geth itself, which ignores this arg too. + _ = codeLen + + nonce := account.Nonce + b := account.Balance.Clone() + codeHash := common.BytesToHash(account.CodeHash) + out := &AccountDiff{ + Nonce: &nonce, + Balance: b, + Storage: nil, + CodeHash: &codeHash, + } + // preserve the storage diff + if diffAcc, ok := f.diff.Account[address]; ok { + out.Storage = diffAcc.Storage + } + f.diff.Account[address] = out + return nil +} + +func (f *ForkedAccountsTrie) UpdateStorage(addr common.Address, key, value []byte) error { + diffAcc, ok := f.diff.Account[addr] + if !ok { + diffAcc = &AccountDiff{} + f.diff.Account[addr] = diffAcc + } + if diffAcc.Storage == nil { + diffAcc.Storage = make(map[common.Hash]common.Hash) + } + k := common.BytesToHash(key) + v := common.BytesToHash(value) + diffAcc.Storage[k] = v + return nil +} + +func (f *ForkedAccountsTrie) DeleteAccount(address common.Address) error { + f.diff.Account[address] = nil + return nil +} + +func (f *ForkedAccountsTrie) DeleteStorage(addr common.Address, key []byte) error { + return f.UpdateStorage(addr, key, nil) +} + +func (f *ForkedAccountsTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error { + diffAcc, ok := f.diff.Account[addr] + if !ok { + diffAcc = &AccountDiff{} + f.diff.Account[addr] = diffAcc + } + diffAcc.CodeHash = &codeHash + f.diff.Code[codeHash] = code + return nil +} + +func (f *ForkedAccountsTrie) Hash() common.Hash { + return f.stateRoot +} + +func (f *ForkedAccountsTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { + panic("cannot commit state-changes of a forked trie") +} + +func (f *ForkedAccountsTrie) Witness() map[string]struct{} { + panic("witness generation of a ForkedAccountsTrie is not supported") +} + +func (f *ForkedAccountsTrie) NodeIterator(startKey []byte) (trie.NodeIterator, error) { + return nil, errors.New("node iteration of a ForkedAccountsTrie is not supported") +} + +func (f *ForkedAccountsTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + return errors.New("proving of a ForkedAccountsTrie is not supported") +} + +func (f *ForkedAccountsTrie) IsVerkle() bool { + return false +} diff --git a/op-chain-ops/script/forking/trie_test.go b/op-chain-ops/script/forking/trie_test.go new file mode 100644 index 0000000000000..6d703814ed4d2 --- /dev/null +++ b/op-chain-ops/script/forking/trie_test.go @@ -0,0 +1,209 @@ +package forking + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +func setupTrie(t *testing.T) (*ForkedAccountsTrie, *MockForkSource) { + mockSource := new(MockForkSource) + stateRoot := common.HexToHash("0x1234") + mockSource.On("StateRoot").Return(stateRoot) + + trie := &ForkedAccountsTrie{ + stateRoot: stateRoot, + src: mockSource, + diff: NewExportDiff(), + } + return trie, mockSource +} + +func TestForkedAccountsTrie_GetAccount(t *testing.T) { + trie, mockSource := setupTrie(t) + addr := common.HexToAddress("0x1234") + + // Setup mock responses + expectedNonce := uint64(1) + expectedBalance := uint256.NewInt(100) + expectedCode := []byte{1, 2, 3, 4} + expectedCodeHash := crypto.Keccak256Hash(expectedCode) + + mockSource.On("Nonce", addr).Return(expectedNonce, nil) + mockSource.On("Balance", addr).Return(expectedBalance, nil) + mockSource.On("Code", addr).Return(expectedCode, nil) + + // Test initial account retrieval + account, err := trie.GetAccount(addr) + require.NoError(t, err) + require.Equal(t, expectedNonce, account.Nonce) + require.Equal(t, expectedBalance, uint256.NewInt(0).SetBytes(account.Balance.Bytes())) + require.Equal(t, expectedCodeHash.Bytes(), account.CodeHash) + + // Update account and verify diff + newNonce := uint64(2) + newBalance := uint256.NewInt(200) + account.Nonce = newNonce + account.Balance = newBalance + + err = trie.UpdateAccount(addr, account, 0) + require.NoError(t, err) + + // Verify updated account + updatedAccount, err := trie.GetAccount(addr) + require.NoError(t, err) + require.Equal(t, newNonce, updatedAccount.Nonce) + require.Equal(t, newBalance, uint256.NewInt(0).SetBytes(updatedAccount.Balance.Bytes())) +} + +func TestForkedAccountsTrie_Storage(t *testing.T) { + trie, mockSource := setupTrie(t) + addr := common.HexToAddress("0x1234") + key := common.HexToHash("0x1") + value := common.HexToHash("0x2") + + // Setup mock for initial storage value + mockSource.On("StorageAt", addr, key).Return(value, nil) + + // Test initial storage retrieval + storageValue, err := trie.GetStorage(addr, key.Bytes()) + require.NoError(t, err) + require.Equal(t, value.Bytes(), storageValue) + + // Update storage + newValue := common.HexToHash("0x3") + err = trie.UpdateStorage(addr, key.Bytes(), newValue.Bytes()) + require.NoError(t, err) + + // Verify updated storage + updatedValue, err := trie.GetStorage(addr, key.Bytes()) + require.NoError(t, err) + require.Equal(t, newValue.Bytes(), updatedValue) +} + +func TestForkedAccountsTrie_ContractCode(t *testing.T) { + trie, mockSource := setupTrie(t) + addr := common.HexToAddress("0x1234") + code := []byte{1, 2, 3, 4} + codeHash := crypto.Keccak256Hash(code) + + // Setup mock for code retrieval + mockSource.On("Code", addr).Return(code, nil) + + // Test initial code retrieval + retrievedCode, err := trie.ContractCode(addr, codeHash) + require.NoError(t, err) + require.Equal(t, code, retrievedCode) + + // Update code + newCode := []byte{5, 6, 7, 8} + newCodeHash := crypto.Keccak256Hash(newCode) + + err = trie.UpdateContractCode(addr, newCodeHash, newCode) + require.NoError(t, err) + + // Verify updated code + updatedCode, err := trie.ContractCode(addr, newCodeHash) + require.NoError(t, err) + require.Equal(t, newCode, updatedCode) +} + +func TestForkedAccountsTrie_DeleteAccount(t *testing.T) { + trie, _ := setupTrie(t) + addr := common.HexToAddress("0x1234") + + // Setup initial account + account := &types.StateAccount{ + Nonce: 1, + Balance: uint256.NewInt(100), + CodeHash: crypto.Keccak256([]byte{1, 2, 3, 4}), + } + + err := trie.UpdateAccount(addr, account, 0) + require.NoError(t, err) + + // Delete account + err = trie.DeleteAccount(addr) + require.NoError(t, err) + + // Verify account is marked as deleted in diff + require.Nil(t, trie.diff.Account[addr]) +} + +func TestForkedAccountsTrie_Copy(t *testing.T) { + trie, _ := setupTrie(t) + addr := common.HexToAddress("0x1234") + + // Setup some initial state + account := &types.StateAccount{ + Nonce: 1, + Balance: uint256.NewInt(100), + CodeHash: crypto.Keccak256([]byte{1, 2, 3, 4}), + } + err := trie.UpdateAccount(addr, account, 0) + require.NoError(t, err) + + // Make a copy + cpy := trie.Copy() + + // Verify copy has same state + require.Equal(t, trie.stateRoot, cpy.stateRoot) + require.Equal(t, trie.diff.Account[addr].Nonce, cpy.diff.Account[addr].Nonce) + require.True(t, trie.diff.Account[addr].Balance.Eq(cpy.diff.Account[addr].Balance)) + + // Modify copy and verify original is unchanged + newAccount := &types.StateAccount{ + Nonce: 2, + Balance: uint256.NewInt(200), + CodeHash: crypto.Keccak256([]byte{5, 6, 7, 8}), + } + err = cpy.UpdateAccount(addr, newAccount, 0) + require.NoError(t, err) + + originalAccount, err := trie.GetAccount(addr) + require.NoError(t, err) + require.Equal(t, uint64(1), originalAccount.Nonce) + require.True(t, uint256.NewInt(100).Eq(uint256.NewInt(0).SetBytes(originalAccount.Balance.Bytes()))) +} + +func TestForkedAccountsTrie_HasDiff(t *testing.T) { + trie, _ := setupTrie(t) + + // Initially no diff + require.False(t, trie.HasDiff()) + + // Add account change + addr := common.HexToAddress("0x1234") + account := &types.StateAccount{ + Nonce: 1, + Balance: uint256.NewInt(100), + CodeHash: crypto.Keccak256([]byte{1, 2, 3, 4}), + } + err := trie.UpdateAccount(addr, account, 0) + require.NoError(t, err) + + // Verify diff exists + require.True(t, trie.HasDiff()) + + // Clear diff + trie.ClearDiff() + require.False(t, trie.HasDiff()) +} + +func TestForkedAccountsTrie_UnsupportedOperations(t *testing.T) { + trie, _ := setupTrie(t) + + require.Panics(t, func() { trie.GetKey([]byte{1, 2, 3}) }) + require.Panics(t, func() { trie.Commit(false) }) + require.Panics(t, func() { trie.Witness() }) + + _, err := trie.NodeIterator(nil) + require.Error(t, err) + + err = trie.Prove(nil, nil) + require.Error(t, err) +} diff --git a/op-chain-ops/script/prank.go b/op-chain-ops/script/prank.go index b6a68f4c44b17..cdea026063a20 100644 --- a/op-chain-ops/script/prank.go +++ b/op-chain-ops/script/prank.go @@ -8,6 +8,8 @@ import ( "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses" + "github.com/holiman/uint256" "github.com/ethereum/go-ethereum/common" @@ -53,7 +55,7 @@ func (h *Host) handleCaller(caller vm.ContractRef) vm.ContractRef { // apply prank, if top call-frame had set up a prank if len(h.callStack) > 0 { parentCallFrame := h.callStack[len(h.callStack)-1] - if parentCallFrame.Prank != nil && caller.Address() != VMAddr { // pranks do not apply to the cheatcode precompile + if parentCallFrame.Prank != nil && caller.Address() != addresses.VMAddr { // pranks do not apply to the cheatcode precompile if parentCallFrame.Prank.Broadcast && parentCallFrame.LastOp == vm.CREATE2 && h.useCreate2Deployer { return &prankRef{ prank: DeterministicDeployerAddress, diff --git a/op-chain-ops/script/precompile.go b/op-chain-ops/script/precompile.go index 2dcea66018834..0ac692be7c1bf 100644 --- a/op-chain-ops/script/precompile.go +++ b/op-chain-ops/script/precompile.go @@ -9,6 +9,8 @@ import ( "reflect" "strings" + "github.com/holiman/uint256" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -352,6 +354,8 @@ type ABIInt256 big.Int var abiInt256Type = typeFor[ABIInt256]() +var abiUint256Type = typeFor[uint256.Int]() + // goTypeToSolidityType converts a Go type to the solidity ABI type definition. // The "internalType" is a quirk of the Geth ABI utils, for nested structures. // Unfortunately we have to convert to string, not directly to ABI type structure, @@ -364,6 +368,9 @@ func goTypeToSolidityType(typ reflect.Type) (typeDef, internalType string, err e reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strings.ToLower(typ.Kind().String()), "", nil case reflect.Array: + if typ.AssignableTo(abiUint256Type) { // uint256.Int underlying Go type is [4]uint64 + return "uint256", "", nil + } if typ.Elem().Kind() == reflect.Uint8 { if typ.Len() == 20 && typ.Name() == "Address" { return "address", "", nil diff --git a/op-chain-ops/script/script.go b/op-chain-ops/script/script.go index 07839b93a6261..9a3d9ae80201d 100644 --- a/op-chain-ops/script/script.go +++ b/op-chain-ops/script/script.go @@ -4,9 +4,11 @@ import ( "bytes" "encoding/binary" "encoding/json" + "errors" "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses" "github.com/holiman/uint256" "github.com/ethereum/go-ethereum/accounts/abi" @@ -19,13 +21,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" "github.com/ethereum-optimism/optimism/op-chain-ops/srcmap" ) @@ -72,9 +74,12 @@ type Host struct { af *foundry.ArtifactsFS chainCfg *params.ChainConfig env *vm.EVM - state *state.StateDB - stateDB state.Database - rawDB ethdb.Database + + state *forking.ForkableState + baseState *state.StateDB + + // only known contracts may utilize cheatcodes and logging + allowedCheatcodes map[common.Address]struct{} cheatcodes *Precompile[*CheatCodesPrecompile] console *Precompile[*ConsolePrecompile] @@ -117,6 +122,7 @@ type BroadcastHook func(broadcast Broadcast) type Hooks struct { OnBroadcast BroadcastHook + OnFork ForkHook } func WithBroadcastHook(hook BroadcastHook) HostOption { @@ -125,6 +131,12 @@ func WithBroadcastHook(hook BroadcastHook) HostOption { } } +func WithForkHook(hook ForkHook) HostOption { + return func(h *Host) { + h.hooks.OnFork = hook + } +} + // WithIsolatedBroadcasts makes each broadcast clean the context, // by flushing the dirty storage changes, and preparing the ephemeral state again. // This then produces more accurate gas estimation for broadcast calls. @@ -167,7 +179,11 @@ func NewHost( srcMaps: make(map[common.Address]*srcmap.SourceMap), hooks: &Hooks{ OnBroadcast: func(broadcast Broadcast) {}, + OnFork: func(opts *ForkConfig) (forking.ForkSource, error) { + return nil, errors.New("no forking configured") + }, }, + allowedCheatcodes: make(map[common.Address]struct{}), } for _, opt := range options { @@ -206,23 +222,25 @@ func NewHost( EcotoneTime: nil, FjordTime: nil, GraniteTime: nil, + HoloceneTime: nil, InteropTime: nil, Optimism: nil, } // Create an in-memory database, to host our temporary script state changes - h.rawDB = rawdb.NewMemoryDatabase() - h.stateDB = state.NewDatabase(triedb.NewDatabase(h.rawDB, &triedb.Config{ + rawDB := rawdb.NewMemoryDatabase() + stateDB := state.NewDatabase(triedb.NewDatabase(rawDB, &triedb.Config{ Preimages: true, // To be able to iterate the state we need the Preimages IsVerkle: false, HashDB: hashdb.Defaults, PathDB: nil, }), nil) var err error - h.state, err = state.New(types.EmptyRootHash, h.stateDB) + h.baseState, err = state.New(types.EmptyRootHash, stateDB) if err != nil { panic(fmt.Errorf("failed to create memory state db: %w", err)) } + h.state = forking.NewForkableState(h.baseState) // Initialize a block-context for the EVM to access environment variables. // The block context (after embedding inside of the EVM environment) may be mutated later. @@ -251,7 +269,7 @@ func NewHost( GasPrice: big.NewInt(0), BlobHashes: executionContext.BlobHashes, BlobFeeCap: big.NewInt(0), - AccessEvents: state.NewAccessEvents(h.stateDB.PointCache()), + AccessEvents: state.NewAccessEvents(h.baseState.PointCache()), } // Hook up the Host to capture the EVM environment changes @@ -277,6 +295,18 @@ func NewHost( return h } +// AllowCheatcodes allows the given address to utilize the cheatcodes and logging precompiles +func (h *Host) AllowCheatcodes(addr common.Address) { + h.log.Debug("Allowing cheatcodes", "address", addr, "label", h.labels[addr]) + h.allowedCheatcodes[addr] = struct{}{} +} + +// AllowedCheatcodes returns whether the given address is allowed to use cheatcodes +func (h *Host) AllowedCheatcodes(addr common.Address) bool { + _, ok := h.allowedCheatcodes[addr] + return ok +} + // EnableCheats enables the Forge/HVM cheat-codes precompile and the Hardhat-style console2 precompile. func (h *Host) EnableCheats() error { vmPrecompile, err := NewPrecompile[*CheatCodesPrecompile](&CheatCodesPrecompile{h: h}) @@ -287,8 +317,8 @@ func (h *Host) EnableCheats() error { // Solidity does EXTCODESIZE checks on functions without return-data. // We need to insert some placeholder code to prevent it from aborting calls. // Emulates Forge script: https://github.com/foundry-rs/foundry/blob/224fe9cbf76084c176dabf7d3b2edab5df1ab818/crates/evm/evm/src/executors/mod.rs#L108 - h.state.SetCode(VMAddr, []byte{0x00}) - h.precompiles[VMAddr] = h.cheatcodes + h.state.SetCode(addresses.VMAddr, []byte{0x00}) + h.precompiles[addresses.VMAddr] = h.cheatcodes consolePrecompile, err := NewPrecompile[*ConsolePrecompile](&ConsolePrecompile{ logger: h.log, @@ -298,7 +328,7 @@ func (h *Host) EnableCheats() error { return fmt.Errorf("failed to init console precompile: %w", err) } h.console = consolePrecompile - h.precompiles[ConsoleAddr] = h.console + h.precompiles[addresses.ConsoleAddr] = h.console // The Console precompile does not need bytecode, // calls all go through a console lib, which avoids the EXTCODESIZE. return nil @@ -413,7 +443,7 @@ func (h *Host) ImportAccount(addr common.Address, account types.Account) { // getPrecompile overrides any accounts during runtime, to insert special precompiles, if activated. func (h *Host) getPrecompile(rules params.Rules, original vm.PrecompiledContract, addr common.Address) vm.PrecompiledContract { if p, ok := h.precompiles[addr]; ok { - return p + return &AccessControlledPrecompile{h: h, inner: p} } return original } @@ -439,6 +469,11 @@ func (h *Host) HasPrecompileOverride(addr common.Address) bool { return ok } +// GetCode returns the code of an account from the state. +func (h *Host) GetCode(addr common.Address) []byte { + return h.state.GetCode(addr) +} + // onEnter is a trace-hook, which we use to apply changes to the state-DB, to simulate isolated broadcast calls, // for better gas estimation of the exact broadcast call execution. func (h *Host) onEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { @@ -456,7 +491,7 @@ func (h *Host) onEnter(depth int, typ byte, from common.Address, to common.Addre if !parentCallFrame.Prank.Broadcast { return } - if to == VMAddr || to == ConsoleAddr { // no broadcasts to the cheatcode or console address + if to == addresses.VMAddr || to == addresses.ConsoleAddr { // no broadcasts to the cheatcode or console address return } @@ -555,6 +590,13 @@ func (h *Host) unwindCallstack(depth int) { func (h *Host) onOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { h.unwindCallstack(depth) scopeCtx := scope.(*vm.ScopeContext) + if scopeCtx.Contract.IsDeployment { + // If we are not yet allowed access to cheatcodes, but if the caller is, + // and if this is a contract-creation, then we are automatically granted cheatcode access. + if !h.AllowedCheatcodes(scopeCtx.Address()) && h.AllowedCheatcodes(scopeCtx.Caller()) { + h.AllowCheatcodes(scopeCtx.Address()) + } + } // Check if we are entering a new depth, add it to the call-stack if so. // We do this here, instead of onEnter, to capture an initialized scope. if len(h.callStack) == 0 || h.callStack[len(h.callStack)-1].Depth < depth { @@ -567,7 +609,7 @@ func (h *Host) onOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpCo }) } // Sanity check that top of the call-stack matches the scope context now - if len(h.callStack) == 0 || h.callStack[len(h.callStack)-1].Ctx != scopeCtx { + if h.callStack[len(h.callStack)-1].Ctx != scopeCtx { panic("scope context changed without call-frame pop/push") } cf := h.callStack[len(h.callStack)-1] @@ -603,11 +645,11 @@ func (h *Host) onLog(ev *types.Log) { // CurrentCall returns the top of the callstack. Or zeroed if there was no call frame yet. // If zeroed, the call-frame has a nil scope context. -func (h *Host) CurrentCall() CallFrame { +func (h *Host) CurrentCall() *CallFrame { if len(h.callStack) == 0 { - return CallFrame{} + return &CallFrame{} } - return *h.callStack[len(h.callStack)-1] + return h.callStack[len(h.callStack)-1] } // MsgSender returns the msg.sender of the current active EVM call-frame, @@ -646,37 +688,62 @@ func (h *Host) SetEnvVar(key string, value string) { // After flushing the EVM state also cannot revert to a previous snapshot state: // the state should not be dumped within contract-execution that needs to revert. func (h *Host) StateDump() (*foundry.ForgeAllocs, error) { + if id, ok := h.state.ActiveFork(); ok { + return nil, fmt.Errorf("cannot state-dump while fork %s is active", id) + } + baseState := h.baseState // We have to commit the existing state to the trie, // for all the state-changes to be captured by the trie iterator. - root, err := h.state.Commit(h.env.Context.BlockNumber.Uint64(), true) + root, err := baseState.Commit(h.env.Context.BlockNumber.Uint64(), true) if err != nil { return nil, fmt.Errorf("failed to commit state: %w", err) } // We need a state object around the state DB - st, err := state.New(root, h.stateDB) + st, err := state.New(root, baseState.Database()) if err != nil { return nil, fmt.Errorf("failed to create state object for state-dumping: %w", err) } // After Commit we cannot reuse the old State, so we update the host to use the new one - h.state = st - h.env.StateDB = st + h.baseState = st + h.state.SubstituteBaseState(st) + // We use the new state object for state-dumping & future state-access, wrapped around + // the just committed trie that has all changes in it. + // I.e. the trie is committed and ready to provide all data, + // and the state is new and iterable, prepared specifically for FromState(state). var allocs foundry.ForgeAllocs allocs.FromState(st) // Sanity check we have no lingering scripts. - for i := uint64(0); i <= allocs.Accounts[ScriptDeployer].Nonce; i++ { - scriptAddr := crypto.CreateAddress(ScriptDeployer, i) + for i := uint64(0); i <= allocs.Accounts[addresses.ScriptDeployer].Nonce; i++ { + scriptAddr := crypto.CreateAddress(addresses.ScriptDeployer, i) h.log.Info("removing script from state-dump", "addr", scriptAddr, "label", h.labels[scriptAddr]) delete(allocs.Accounts, scriptAddr) } + // Clean out empty storage slots in the dump - this is necessary for compatibility + // with the superchain registry. + for _, account := range allocs.Accounts { + toDelete := make([]common.Hash, 0) + + for slot, value := range account.Storage { + if value == (common.Hash{}) { + toDelete = append(toDelete, slot) + } + } + + for _, slot := range toDelete { + delete(account.Storage, slot) + } + } + // Remove the script deployer from the output - delete(allocs.Accounts, ScriptDeployer) + delete(allocs.Accounts, addresses.ScriptDeployer) + delete(allocs.Accounts, addresses.ForgeDeployer) // The cheatcodes VM has a placeholder bytecode, // because solidity checks if the code exists prior to regular EVM-calls to it. - delete(allocs.Accounts, VMAddr) + delete(allocs.Accounts, addresses.VMAddr) // Precompile overrides come with temporary state account placeholders. Ignore those. for addr := range h.precompiles { @@ -753,7 +820,7 @@ func (h *Host) Label(addr common.Address, label string) { // NewScriptAddress creates a new address for the ScriptDeployer account, and bumps the nonce. func (h *Host) NewScriptAddress() common.Address { - deployer := ScriptDeployer + deployer := addresses.ScriptDeployer deployNonce := h.state.GetNonce(deployer) // compute address of script contract to be deployed addr := crypto.CreateAddress(deployer, deployNonce) @@ -782,3 +849,15 @@ func (h *Host) RememberOnLabel(label, srcFile, contract string) error { }) return nil } + +func (h *Host) CreateSelectFork(opts ...ForkOption) (*big.Int, error) { + src, err := h.onFork(opts...) + if err != nil { + return nil, fmt.Errorf("failed to setup fork source: %w", err) + } + id, err := h.state.CreateSelectFork(src) + if err != nil { + return nil, fmt.Errorf("failed to create-select fork: %w", err) + } + return id.U256().ToBig(), nil +} diff --git a/op-chain-ops/script/script_test.go b/op-chain-ops/script/script_test.go index 9d029c801060b..48949347b63d2 100644 --- a/op-chain-ops/script/script_test.go +++ b/op-chain-ops/script/script_test.go @@ -2,12 +2,18 @@ package script import ( "bytes" + "context" "encoding/json" "fmt" "math/big" "strings" "testing" + "github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" + "github.com/stretchr/testify/mock" + "github.com/holiman/uint256" "github.com/stretchr/testify/require" @@ -23,16 +29,27 @@ import ( //go:generate ./testdata/generate.sh +// MockRPCClient implements RPCClient interface for testing +type MockRPCClient struct { + mock.Mock +} + +func (m *MockRPCClient) CallContext(ctx context.Context, result any, method string, args ...any) error { + return m.Called(ctx, result, method, args).Error(0) +} + func TestScript(t *testing.T) { logger, captLog := testlog.CaptureLogger(t, log.LevelInfo) af := foundry.OpenArtifactsDir("./testdata/test-artifacts") scriptContext := DefaultContext h := NewHost(logger, af, nil, scriptContext) + require.NoError(t, h.EnableCheats()) + addr, err := h.LoadContract("ScriptExample.s.sol", "ScriptExample") require.NoError(t, err) - - require.NoError(t, h.EnableCheats()) + h.AllowCheatcodes(addr) + t.Logf("allowing %s to access cheatcodes", addr) h.SetEnvVar("EXAMPLE_BOOL", "true") input := bytes4("run()") @@ -45,19 +62,19 @@ func TestScript(t *testing.T) { require.NoError(t, h.cheatcodes.Precompile.DumpState("noop")) } +func mustEncodeStringCalldata(t *testing.T, method, input string) []byte { + packer, err := abi.JSON(strings.NewReader(fmt.Sprintf(`[{"type":"function","name":"%s","inputs":[{"type":"string","name":"input"}]}]`, method))) + require.NoError(t, err) + + data, err := packer.Pack(method, input) + require.NoError(t, err) + return data +} + func TestScriptBroadcast(t *testing.T) { logger := testlog.Logger(t, log.LevelDebug) af := foundry.OpenArtifactsDir("./testdata/test-artifacts") - mustEncodeCalldata := func(method, input string) []byte { - packer, err := abi.JSON(strings.NewReader(fmt.Sprintf(`[{"type":"function","name":"%s","inputs":[{"type":"string","name":"input"}]}]`, method))) - require.NoError(t, err) - - data, err := packer.Pack(method, input) - require.NoError(t, err) - return data - } - fooBar, err := af.ReadArtifact("ScriptExample.s.sol", "FooBar") require.NoError(t, err) @@ -74,7 +91,7 @@ func TestScriptBroadcast(t *testing.T) { { From: scriptAddr, To: scriptAddr, - Input: mustEncodeCalldata("call1", "single_call1"), + Input: mustEncodeStringCalldata(t, "call1", "single_call1"), Value: (*hexutil.U256)(uint256.NewInt(0)), GasUsed: 23421, Type: BroadcastCall, @@ -83,7 +100,7 @@ func TestScriptBroadcast(t *testing.T) { { From: coffeeAddr, To: scriptAddr, - Input: mustEncodeCalldata("call1", "startstop_call1"), + Input: mustEncodeStringCalldata(t, "call1", "startstop_call1"), Value: (*hexutil.U256)(uint256.NewInt(0)), GasUsed: 1521, Type: BroadcastCall, @@ -92,7 +109,7 @@ func TestScriptBroadcast(t *testing.T) { { From: coffeeAddr, To: scriptAddr, - Input: mustEncodeCalldata("call2", "startstop_call2"), + Input: mustEncodeStringCalldata(t, "call2", "startstop_call2"), Value: (*hexutil.U256)(uint256.NewInt(0)), GasUsed: 1565, Type: BroadcastCall, @@ -101,7 +118,7 @@ func TestScriptBroadcast(t *testing.T) { { From: common.HexToAddress("0x1234"), To: scriptAddr, - Input: mustEncodeCalldata("nested1", "nested"), + Input: mustEncodeStringCalldata(t, "nested1", "nested"), Value: (*hexutil.U256)(uint256.NewInt(0)), GasUsed: 2763, Type: BroadcastCall, @@ -142,10 +159,11 @@ func TestScriptBroadcast(t *testing.T) { broadcasts = append(broadcasts, broadcast) } h := NewHost(logger, af, nil, DefaultContext, WithBroadcastHook(hook), WithCreate2Deployer()) + require.NoError(t, h.EnableCheats()) + addr, err := h.LoadContract("ScriptExample.s.sol", "ScriptExample") require.NoError(t, err) - - require.NoError(t, h.EnableCheats()) + h.AllowCheatcodes(addr) input := bytes4("runBroadcast()") returnData, _, err := h.Call(senderAddr, addr, input[:], DefaultFoundryGasLimit, uint256.NewInt(0)) @@ -168,3 +186,163 @@ func TestScriptBroadcast(t *testing.T) { // address that will perform the send to the Create2Deployer. require.EqualValues(t, 1, h.GetNonce(cafeAddr)) } + +func TestScriptStateDump(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + af := foundry.OpenArtifactsDir("./testdata/test-artifacts") + + h := NewHost(logger, af, nil, DefaultContext) + require.NoError(t, h.EnableCheats()) + + addr, err := h.LoadContract("ScriptExample.s.sol", "ScriptExample") + require.NoError(t, err) + h.AllowCheatcodes(addr) + + counterStorageSlot := common.Hash{} + + dump, err := h.StateDump() + require.NoError(t, err, "dump 1") + require.Contains(t, dump.Accounts, addr, "has contract") + require.NotContains(t, dump.Accounts[addr].Storage, counterStorageSlot, "not counted yet") + + dat := mustEncodeStringCalldata(t, "call1", "call A") + returnData, _, err := h.Call(addresses.DefaultSenderAddr, addr, dat, DefaultFoundryGasLimit, uint256.NewInt(0)) + require.NoError(t, err, "call A failed: %x", string(returnData)) + + dump, err = h.StateDump() + require.NoError(t, err, "dump 2") + require.Contains(t, dump.Accounts, addr, "has contract") + require.Equal(t, dump.Accounts[addr].Storage[counterStorageSlot], common.Hash{31: 1}, "counted to 1") + + dat = mustEncodeStringCalldata(t, "call1", "call B") + returnData, _, err = h.Call(addresses.DefaultSenderAddr, addr, dat, DefaultFoundryGasLimit, uint256.NewInt(0)) + require.NoError(t, err, "call B failed: %x", string(returnData)) + + dump, err = h.StateDump() + require.NoError(t, err, "dump 3") + require.Contains(t, dump.Accounts, addr, "has contract") + require.Equal(t, dump.Accounts[addr].Storage[counterStorageSlot], common.Hash{31: 2}, "counted to 2") +} + +type forkConfig struct { + blockNum uint64 + stateRoot common.Hash + blockHash common.Hash + nonce uint64 + storageValue *big.Int + code []byte + balance uint64 +} + +func TestForkingScript(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + af := foundry.OpenArtifactsDir("./testdata/test-artifacts") + + forkedContract, err := af.ReadArtifact("ScriptExample.s.sol", "ForkedContract") + require.NoError(t, err) + code := forkedContract.DeployedBytecode.Object + + fork1Config := forkConfig{ + blockNum: 12345, + stateRoot: common.HexToHash("0x1111"), + blockHash: common.HexToHash("0x2222"), + nonce: 12345, + storageValue: big.NewInt(1), + code: code, + balance: 1, + } + + fork2Config := forkConfig{ + blockNum: 23456, + stateRoot: common.HexToHash("0x3333"), + blockHash: common.HexToHash("0x4444"), + nonce: 23456, + storageValue: big.NewInt(2), + code: code, + balance: 2, + } + + // Map of URL/alias to RPC client + rpcClients := map[string]*MockRPCClient{ + "fork1": setupMockRPC(fork1Config), + "fork2": setupMockRPC(fork2Config), + } + forkHook := func(opts *ForkConfig) (forking.ForkSource, error) { + client, ok := rpcClients[opts.URLOrAlias] + if !ok { + return nil, fmt.Errorf("unknown fork URL/alias: %s", opts.URLOrAlias) + } + return forking.RPCSourceByNumber(opts.URLOrAlias, client, *opts.BlockNumber) + } + + scriptContext := DefaultContext + h := NewHost(logger, af, nil, scriptContext, WithForkHook(forkHook)) + require.NoError(t, h.EnableCheats()) + + addr, err := h.LoadContract("ScriptExample.s.sol", "ForkTester") + require.NoError(t, err) + h.AllowCheatcodes(addr) + // Make this script excluded so it doesn't call the fork RPC. + h.state.MakeExcluded(addr) + t.Logf("allowing %s to access cheatcodes", addr) + + input := bytes4("run()") + returnData, _, err := h.Call(scriptContext.Sender, addr, input[:], DefaultFoundryGasLimit, uint256.NewInt(0)) + require.NoError(t, err, "call failed: %x", string(returnData)) + + for _, client := range rpcClients { + client.AssertExpectations(t) + } +} + +// setupMockRPC creates a mock RPC client with the specified fork configuration +func setupMockRPC(config forkConfig) *MockRPCClient { + mockRPC := new(MockRPCClient) + testAddr := common.HexToAddress("0x1234") + + forkArgs := []any{testAddr, config.blockHash} + + // Mock block header + mockRPC.On("CallContext", mock.Anything, mock.AnythingOfType("**forking.Header"), + "eth_getBlockByNumber", []any{hexutil.Uint64(config.blockNum), false}). + Run(func(args mock.Arguments) { + result := args.Get(1).(**forking.Header) + *result = &forking.Header{ + StateRoot: config.stateRoot, + BlockHash: config.blockHash, + } + }).Return(nil).Once() + + mockRPC.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Uint64"), + "eth_getTransactionCount", forkArgs). + Run(func(args mock.Arguments) { + result := args.Get(1).(*hexutil.Uint64) + *result = hexutil.Uint64(config.nonce) + }).Return(nil) + + // Mock balance + mockRPC.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.U256"), + "eth_getBalance", forkArgs). + Run(func(args mock.Arguments) { + result := args.Get(1).(*hexutil.U256) + *result = hexutil.U256(*uint256.NewInt(config.balance)) + }).Return(nil) + + // Mock contract code + mockRPC.On("CallContext", mock.Anything, mock.AnythingOfType("*hexutil.Bytes"), + "eth_getCode", forkArgs). + Run(func(args mock.Arguments) { + result := args.Get(1).(*hexutil.Bytes) + *result = config.code + }).Return(nil) + + // Mock storage value + mockRPC.On("CallContext", mock.Anything, mock.AnythingOfType("*common.Hash"), + "eth_getStorageAt", []any{testAddr, common.Hash{}, config.blockHash}). + Run(func(args mock.Arguments) { + result := args.Get(1).(*common.Hash) + *result = common.BigToHash(config.storageValue) + }).Return(nil) + + return mockRPC +} diff --git a/op-chain-ops/script/testdata/scripts/ScriptExample.s.sol b/op-chain-ops/script/testdata/scripts/ScriptExample.s.sol index f2f20e5ca14e9..2f636afb2be3e 100644 --- a/op-chain-ops/script/testdata/scripts/ScriptExample.s.sol +++ b/op-chain-ops/script/testdata/scripts/ScriptExample.s.sol @@ -13,15 +13,21 @@ interface Vm { function startBroadcast(address msgSender) external; function startBroadcast() external; function stopBroadcast() external; + function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); + function etch(address target, bytes calldata newRuntimeBytecode) external; + function allowCheatcodes(address account) external; + function createSelectFork(string calldata forkName, uint256 blockNumber) external returns (uint256); } // console is a minimal version of the console2 lib. library console { address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); - function _castLogPayloadViewToPure( - function(bytes memory) internal view fnIn - ) internal pure returns (function(bytes memory) internal pure fnOut) { + function _castLogPayloadViewToPure(function(bytes memory) internal view fnIn) + internal + pure + returns (function(bytes memory) internal pure fnOut) + { assembly { fnOut := fnIn } @@ -42,30 +48,29 @@ library console { } function log(string memory p0) internal pure { - _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); + _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); // nosemgrep: sol-style-use-abi-encodecall } function log(string memory p0, bool p1) internal pure { - _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); + _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); // nosemgrep: sol-style-use-abi-encodecall } function log(string memory p0, uint256 p1) internal pure { - _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1)); + _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1)); // nosemgrep: sol-style-use-abi-encodecall } function log(string memory p0, address p1) internal pure { - _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); + _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); // nosemgrep: sol-style-use-abi-encodecall } function log(string memory p0, string memory p1, string memory p2) internal pure { - _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); + _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); // nosemgrep: sol-style-use-abi-encodecall } } /// @title ScriptExample /// @notice ScriptExample is an example script. The Go forge script code tests that it can run this. contract ScriptExample { - address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); Vm internal constant vm = Vm(VM_ADDRESS); @@ -95,6 +100,13 @@ contract ScriptExample { vm.stopPrank(); this.hello("from original again"); + // vm.etch should not give cheatcode access, unless allowed to afterwards + address tmpNonceGetter = address(uint160(uint256(keccak256("temp nonce test getter")))); + vm.etch(tmpNonceGetter, vm.getDeployedCode("ScriptExample.s.sol:NonceGetter")); + vm.allowCheatcodes(tmpNonceGetter); + uint256 v = NonceGetter(tmpNonceGetter).getNonce(address(this)); + console.log("nonce from nonce getter, no explicit access required with vm.etch:", v); + console.log("done!"); } @@ -123,12 +135,12 @@ contract ScriptExample { console.log("contract deployment"); vm.broadcast(address(uint160(0x123456))); FooBar x = new FooBar(1234); - require(x.foo() == 1234); + require(x.foo() == 1234, "FooBar: foo in create is not 1234"); console.log("create 2"); vm.broadcast(address(uint160(0xcafe))); FooBar y = new FooBar{salt: bytes32(uint256(42))}(1234); - require(y.foo() == 1234); + require(y.foo() == 1234, "FooBar: foo in create2 is not 1234"); console.log("done!"); // Deploy a script without a pranked sender and check the nonce. @@ -176,3 +188,44 @@ contract FooBar { foo = v; } } + +contract NonceGetter { + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm internal constant vm = Vm(VM_ADDRESS); + + function getNonce(address _addr) public view returns (uint256) { + return vm.getNonce(_addr); + } +} + +contract ForkedContract { + uint256 internal v; + + constructor() { + v = 1; + } + + function getValue() public view returns (uint256) { + return v; + } +} + +contract ForkTester { + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm internal constant vm = Vm(VM_ADDRESS); + + function run() external { + address testAddr = address(uint160(0x1234)); + ForkedContract fc = ForkedContract(testAddr); + + vm.createSelectFork("fork1", 12345); + require(vm.getNonce(testAddr) == 12345, "nonce should be 12345"); + require(fc.getValue() == 1, "value should be 1"); + require(testAddr.balance == uint256(1), "balance should be 1"); + + vm.createSelectFork("fork2", 23456); + require(vm.getNonce(testAddr) == 23456, "nonce should be 12345"); + require(fc.getValue() == 2, "value should be 2"); + require(testAddr.balance == uint256(2), "balance should be 2"); + } +} \ No newline at end of file diff --git a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/FooBar.json b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/FooBar.json index ad4ac1569433c..249588154e111 100644 --- a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/FooBar.json +++ b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/FooBar.json @@ -1 +1 @@ -{"abi":[{"type":"constructor","inputs":[{"name":"v","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"foo","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b506040516100b23803806100b283398101604081905261002f91610037565b600055610050565b60006020828403121561004957600080fd5b5051919050565b60548061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000a","sourceMap":"5902:96:0:-:0;;;5949:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5982:3;:7;5902:96;;14:184:1;84:6;137:2;125:9;116:7;112:23;108:32;105:52;;;153:1;150;143:12;105:52;-1:-1:-1;176:16:1;;14:184;-1:-1:-1;14:184:1:o;:::-;5902:96:0;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000a","sourceMap":"5902:96:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5924:18;;;;;;;;;160:25:1;;;148:2;133:18;5924::0;;;;;;","linkReferences":{}},"methodIdentifiers":{"foo()":"c2985578"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"v\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"foo\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"FooBar\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3\",\"dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"uint256","name":"v","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"stateMutability":"view","type":"function","name":"foo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"FooBar"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d","urls":["bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3","dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[{"astId":708,"contract":"scripts/ScriptExample.s.sol:FooBar","label":"foo","offset":0,"slot":"0","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":720,"exportedSymbols":{"FooBar":[719],"ScriptExample":[706],"Vm":[55],"console":[192]},"nodeType":"SourceUnit","src":"32:5967:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":55,"nodeType":"ContractDefinition","src":"120:616:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":55,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[55],"name":"Vm","nameLocation":"130:2:0","scope":720,"usedErrors":[]},{"id":192,"nodeType":"ContractDefinition","src":"791:1622:0","nodes":[{"id":61,"nodeType":"VariableDeclaration","src":"813:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"830:15:0","scope":192,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":56,"name":"address","nodeType":"ElementaryTypeName","src":"813:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":59,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"856:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":58,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"848:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":57,"name":"address","nodeType":"ElementaryTypeName","src":"848:7:0","typeDescriptions":{}}},"id":60,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"848:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":78,"nodeType":"FunctionDefinition","src":"906:221:0","nodes":[],"body":{"id":77,"nodeType":"Block","src":"1065:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1084:37:0","statements":[{"nodeType":"YulAssignment","src":"1098:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1107:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1098:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":67,"isOffset":false,"isSlot":false,"src":"1107:4:0","valueSize":1},{"declaration":74,"isOffset":false,"isSlot":false,"src":"1098:5:0","valueSize":1}],"id":76,"nodeType":"InlineAssembly","src":"1075:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"915:25:0","parameters":{"id":68,"nodeType":"ParameterList","parameters":[{"constant":false,"id":67,"mutability":"mutable","name":"fnIn","nameLocation":"987:4:0","nodeType":"VariableDeclaration","scope":78,"src":"950:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":66,"nodeType":"FunctionTypeName","parameterTypes":{"id":64,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":66,"src":"959:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":62,"name":"bytes","nodeType":"ElementaryTypeName","src":"959:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"958:14:0"},"returnParameterTypes":{"id":65,"nodeType":"ParameterList","parameters":[],"src":"987:0:0"},"src":"950:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"940:57:0"},"returnParameters":{"id":75,"nodeType":"ParameterList","parameters":[{"constant":false,"id":74,"mutability":"mutable","name":"fnOut","nameLocation":"1058:5:0","nodeType":"VariableDeclaration","scope":78,"src":"1021:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":73,"nodeType":"FunctionTypeName","parameterTypes":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":73,"src":"1030:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":69,"name":"bytes","nodeType":"ElementaryTypeName","src":"1030:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1029:14:0"},"returnParameterTypes":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"1058:0:0"},"src":"1021:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1020:44:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":90,"nodeType":"FunctionDefinition","src":"1133:133:0","nodes":[],"body":{"id":89,"nodeType":"Block","src":"1194:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":86,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":80,"src":"1251:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":84,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1230:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":83,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":78,"src":"1204:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":85,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":87,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":88,"nodeType":"ExpressionStatement","src":"1204:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1142:15:0","parameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"payload","nameLocation":"1171:7:0","nodeType":"VariableDeclaration","scope":90,"src":"1158:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":79,"name":"bytes","nodeType":"ElementaryTypeName","src":"1158:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1157:22:0"},"returnParameters":{"id":82,"nodeType":"ParameterList","parameters":[],"src":"1194:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1272:380:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1336:316:0","nodes":[],"statements":[{"assignments":[96],"declarations":[{"constant":false,"id":96,"mutability":"mutable","name":"payloadLength","nameLocation":"1354:13:0","nodeType":"VariableDeclaration","scope":105,"src":"1346:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":95,"name":"uint256","nodeType":"ElementaryTypeName","src":"1346:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":99,"initialValue":{"expression":{"id":97,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":92,"src":"1370:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":98,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1370:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1346:38:0"},{"assignments":[101],"declarations":[{"constant":false,"id":101,"mutability":"mutable","name":"consoleAddress","nameLocation":"1402:14:0","nodeType":"VariableDeclaration","scope":105,"src":"1394:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":100,"name":"address","nodeType":"ElementaryTypeName","src":"1394:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":103,"initialValue":{"id":102,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":61,"src":"1419:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1394:40:0"},{"AST":{"nodeType":"YulBlock","src":"1496:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1510:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1534:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1543:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1530:3:0"},"nodeType":"YulFunctionCall","src":"1530:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1514:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1559:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1579:3:0"},"nodeType":"YulFunctionCall","src":"1579:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1586:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1602:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1616:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1631:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"1634:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1568:10:0"},"nodeType":"YulFunctionCall","src":"1568:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1563:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":101,"isOffset":false,"isSlot":false,"src":"1586:14:0","valueSize":1},{"declaration":92,"isOffset":false,"isSlot":false,"src":"1534:7:0","valueSize":1},{"declaration":96,"isOffset":false,"isSlot":false,"src":"1616:13:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1487:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1281:19:0","parameters":{"id":93,"nodeType":"ParameterList","parameters":[{"constant":false,"id":92,"mutability":"mutable","name":"payload","nameLocation":"1314:7:0","nodeType":"VariableDeclaration","scope":106,"src":"1301:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":91,"name":"bytes","nodeType":"ElementaryTypeName","src":"1301:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1300:22:0"},"returnParameters":{"id":94,"nodeType":"ParameterList","parameters":[],"src":"1336:0:0"},"scope":192,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":120,"nodeType":"FunctionDefinition","src":"1658:121:0","nodes":[],"body":{"id":119,"nodeType":"Block","src":"1703:76:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":114,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1753:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":115,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1768:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":112,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1729:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":113,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1729: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":116,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1729:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":111,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1713:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":117,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1713:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":118,"nodeType":"ExpressionStatement","src":"1713:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1667:3:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"p0","nameLocation":"1685:2:0","nodeType":"VariableDeclaration","scope":120,"src":"1671:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":107,"name":"string","nodeType":"ElementaryTypeName","src":"1671:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"1670:18:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":137,"nodeType":"FunctionDefinition","src":"1785:139:0","nodes":[],"body":{"id":136,"nodeType":"Block","src":"1839:85:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":130,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1889:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":131,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":122,"src":"1909:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":132,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":124,"src":"1913:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":128,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1865:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":129,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1865: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":133,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1865:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":127,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1849:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":134,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1849:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":135,"nodeType":"ExpressionStatement","src":"1849:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1794:3:0","parameters":{"id":125,"nodeType":"ParameterList","parameters":[{"constant":false,"id":122,"mutability":"mutable","name":"p0","nameLocation":"1812:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1798:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":121,"name":"string","nodeType":"ElementaryTypeName","src":"1798:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":124,"mutability":"mutable","name":"p1","nameLocation":"1821:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1816:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":123,"name":"bool","nodeType":"ElementaryTypeName","src":"1816:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"1797:27:0"},"returnParameters":{"id":126,"nodeType":"ParameterList","parameters":[],"src":"1839:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":154,"nodeType":"FunctionDefinition","src":"1930:145:0","nodes":[],"body":{"id":153,"nodeType":"Block","src":"1987:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":147,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2037:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":148,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":139,"src":"2060:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":149,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":141,"src":"2064:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":145,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2013:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":146,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2013: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":150,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2013:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":144,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1997:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":151,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1997:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":152,"nodeType":"ExpressionStatement","src":"1997:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1939:3:0","parameters":{"id":142,"nodeType":"ParameterList","parameters":[{"constant":false,"id":139,"mutability":"mutable","name":"p0","nameLocation":"1957:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1943:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":138,"name":"string","nodeType":"ElementaryTypeName","src":"1943:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":141,"mutability":"mutable","name":"p1","nameLocation":"1969:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1961:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":140,"name":"uint256","nodeType":"ElementaryTypeName","src":"1961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1942:30:0"},"returnParameters":{"id":143,"nodeType":"ParameterList","parameters":[],"src":"1987:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":171,"nodeType":"FunctionDefinition","src":"2081:145:0","nodes":[],"body":{"id":170,"nodeType":"Block","src":"2138:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":164,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2188:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":165,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":156,"src":"2211:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":166,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":158,"src":"2215:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":162,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2164:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":163,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2164: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":167,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2164:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":161,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2148:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":168,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2148:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":169,"nodeType":"ExpressionStatement","src":"2148:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2090:3:0","parameters":{"id":159,"nodeType":"ParameterList","parameters":[{"constant":false,"id":156,"mutability":"mutable","name":"p0","nameLocation":"2108:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2094:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":155,"name":"string","nodeType":"ElementaryTypeName","src":"2094:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":158,"mutability":"mutable","name":"p1","nameLocation":"2120:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2112:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":157,"name":"address","nodeType":"ElementaryTypeName","src":"2112:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2093:30:0"},"returnParameters":{"id":160,"nodeType":"ParameterList","parameters":[],"src":"2138:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":191,"nodeType":"FunctionDefinition","src":"2232:179:0","nodes":[],"body":{"id":190,"nodeType":"Block","src":"2313:98:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":183,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2363:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":184,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":173,"src":"2392:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":185,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":175,"src":"2396:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":186,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":177,"src":"2400:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":181,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2339:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":182,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2339: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":187,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2339:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":180,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2323:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":188,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2323:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":189,"nodeType":"ExpressionStatement","src":"2323:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2241:3:0","parameters":{"id":178,"nodeType":"ParameterList","parameters":[{"constant":false,"id":173,"mutability":"mutable","name":"p0","nameLocation":"2259:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2245:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":172,"name":"string","nodeType":"ElementaryTypeName","src":"2245:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":175,"mutability":"mutable","name":"p1","nameLocation":"2277:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2263:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":174,"name":"string","nodeType":"ElementaryTypeName","src":"2263:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":177,"mutability":"mutable","name":"p2","nameLocation":"2295:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2281:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":176,"name":"string","nodeType":"ElementaryTypeName","src":"2281:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2244:54:0"},"returnParameters":{"id":179,"nodeType":"ParameterList","parameters":[],"src":"2313:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[192],"name":"console","nameLocation":"799:7:0","scope":720,"usedErrors":[]},{"id":706,"nodeType":"ContractDefinition","src":"2541:3359:0","nodes":[{"id":207,"nodeType":"VariableDeclaration","src":"2571:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"2597:10:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":194,"name":"address","nodeType":"ElementaryTypeName","src":"2571:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":202,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2644:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":201,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"2634:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":203,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2634:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":200,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2626:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":199,"name":"uint256","nodeType":"ElementaryTypeName","src":"2626:7:0","typeDescriptions":{}}},"id":204,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2626:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":198,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2618:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":197,"name":"uint160","nodeType":"ElementaryTypeName","src":"2618:7:0","typeDescriptions":{}}},"id":205,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2618:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":196,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2610:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":195,"name":"address","nodeType":"ElementaryTypeName","src":"2610:7:0","typeDescriptions":{}}},"id":206,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2610:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":213,"nodeType":"VariableDeclaration","src":"2671:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"2692:2:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"},"typeName":{"id":209,"nodeType":"UserDefinedTypeName","pathNode":{"id":208,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":55,"src":"2671:2:0"},"referencedDeclaration":55,"src":"2671:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"value":{"arguments":[{"id":211,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":207,"src":"2700:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":210,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":55,"src":"2697:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$55_$","typeString":"type(contract Vm)"}},"id":212,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2697:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"visibility":"internal"},{"id":215,"nodeType":"VariableDeclaration","src":"2775:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"2790:7:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":214,"name":"uint256","nodeType":"ElementaryTypeName","src":"2775:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":378,"nodeType":"FunctionDefinition","src":"2887:949:0","nodes":[],"body":{"id":377,"nodeType":"Block","src":"2909:927:0","nodes":[],"statements":[{"assignments":[220],"declarations":[{"constant":false,"id":220,"mutability":"mutable","name":"x","nameLocation":"2924:1:0","nodeType":"VariableDeclaration","scope":377,"src":"2919:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":219,"name":"bool","nodeType":"ElementaryTypeName","src":"2919:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":226,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":223,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2937:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":224,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"2953:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":221,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"2928:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":222,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"2928:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":225,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2928:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"2919:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2981:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":231,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3004:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":227,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"2969:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":229,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":137,"src":"2969:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":232,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2969:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":233,"nodeType":"ExpressionStatement","src":"2969:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":237,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3029:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":240,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3054:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":239,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3046:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":238,"name":"address","nodeType":"ElementaryTypeName","src":"3046:7:0","typeDescriptions":{}}},"id":241,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3046:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":234,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3017:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":236,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3017:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":242,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3017:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":243,"nodeType":"ExpressionStatement","src":"3017:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":247,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3082:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":252,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3120:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":251,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3112:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":250,"name":"address","nodeType":"ElementaryTypeName","src":"3112:7:0","typeDescriptions":{}}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3112:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":248,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3100:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":249,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3100:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":254,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3100:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":244,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3070:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":246,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3070:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":255,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3070:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":256,"nodeType":"ExpressionStatement","src":"3070:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":260,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3149:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":263,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3172:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3172:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":262,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3164:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":261,"name":"address","nodeType":"ElementaryTypeName","src":"3164:7:0","typeDescriptions":{}}},"id":265,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3164:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":257,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3137:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":259,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3137:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":266,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3137:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":267,"nodeType":"ExpressionStatement","src":"3137:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":271,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3206:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":276,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3242:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3242:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":275,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3234:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":274,"name":"address","nodeType":"ElementaryTypeName","src":"3234:7:0","typeDescriptions":{}}},"id":278,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3234:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":272,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3222:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":273,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3222:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":279,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3222:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":268,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3194:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3194:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":280,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3194:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":281,"nodeType":"ExpressionStatement","src":"3194:61:0"},{"assignments":[283],"declarations":[{"constant":false,"id":283,"mutability":"mutable","name":"json","nameLocation":"3280:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3266:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":282,"name":"string","nodeType":"ElementaryTypeName","src":"3266:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":285,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":284,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3287:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3266:55:0"},{"assignments":[290],"declarations":[{"constant":false,"id":290,"mutability":"mutable","name":"keys","nameLocation":"3347:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3331:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":288,"name":"string","nodeType":"ElementaryTypeName","src":"3331:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":289,"nodeType":"ArrayTypeName","src":"3331:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":296,"initialValue":{"arguments":[{"id":293,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":283,"src":"3371:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":294,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3377:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":291,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3354:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3354:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":295,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3354:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3331:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":300,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3414:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":301,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3422:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":303,"indexExpression":{"hexValue":"30","id":302,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3427:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3422:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":304,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3431:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":306,"indexExpression":{"hexValue":"31","id":305,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3436:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3431:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":297,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3402:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":299,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":191,"src":"3402:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3402:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":308,"nodeType":"ExpressionStatement","src":"3402:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3461:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":309,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3450:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":311,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3450:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":313,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3450:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":314,"nodeType":"ExpressionStatement","src":"3450:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3517:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":321,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3509:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":320,"name":"uint160","nodeType":"ElementaryTypeName","src":"3509:7:0","typeDescriptions":{}}},"id":323,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":319,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3501:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":318,"name":"address","nodeType":"ElementaryTypeName","src":"3501:7:0","typeDescriptions":{}}},"id":324,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3501:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":315,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3487:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":317,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"3487:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":325,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3487:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":326,"nodeType":"ExpressionStatement","src":"3487:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3545:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":327,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3534:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":329,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3534:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":331,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3534:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":332,"nodeType":"ExpressionStatement","src":"3534:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":336,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3582:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":339,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3617:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":340,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3617:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":338,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3609:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":337,"name":"address","nodeType":"ElementaryTypeName","src":"3609:7:0","typeDescriptions":{}}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3609:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":333,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3570:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3570:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":342,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3570:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":343,"nodeType":"ExpressionStatement","src":"3570:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":347,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3651:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":350,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3689:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3681:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":348,"name":"address","nodeType":"ElementaryTypeName","src":"3681:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":344,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3639:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":346,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3639:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":352,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3639:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":353,"nodeType":"ExpressionStatement","src":"3639:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":357,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3716:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":354,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3705:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":356,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3705:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":358,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3705:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":359,"nodeType":"ExpressionStatement","src":"3705:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":360,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3741:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":362,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"3741:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3741:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":364,"nodeType":"ExpressionStatement","src":"3741:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":368,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3776:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":365,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3765:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":367,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3765:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3765:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":370,"nodeType":"ExpressionStatement","src":"3765:33:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":374,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3821:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":371,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3809:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":373,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"3809:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":375,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3809:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":376,"nodeType":"ExpressionStatement","src":"3809:20:0"}]},"documentation":{"id":216,"nodeType":"StructuredDocumentation","src":"2804:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"2896:3:0","parameters":{"id":217,"nodeType":"ParameterList","parameters":[],"src":"2899:2:0"},"returnParameters":{"id":218,"nodeType":"ParameterList","parameters":[],"src":"2909:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":609,"nodeType":"FunctionDefinition","src":"3903:1258:0","nodes":[],"body":{"id":608,"nodeType":"Block","src":"3934:1227:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3956:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":392,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3999:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":391,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3991:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":390,"name":"address","nodeType":"ElementaryTypeName","src":"3991:7:0","typeDescriptions":{}}},"id":393,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3991:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3979:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":389,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3979:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":394,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3979:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":387,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3971:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":386,"name":"uint256","nodeType":"ElementaryTypeName","src":"3971:7:0","typeDescriptions":{}}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3971:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":382,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3944:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3944:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":396,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3944:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":397,"nodeType":"ExpressionStatement","src":"3944:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":401,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4030:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":398,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4018:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":400,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4018:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":402,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4018:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":403,"nodeType":"ExpressionStatement","src":"4018:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":404,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4057:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":406,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"4057:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":407,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4057:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":408,"nodeType":"ExpressionStatement","src":"4057:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4092:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":409,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4081:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":411,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4081:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":413,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4081:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":414,"nodeType":"ExpressionStatement","src":"4081:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":418,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4128:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":415,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4117:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":417,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4117:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4117:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":420,"nodeType":"ExpressionStatement","src":"4117:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":424,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4166:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":421,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4154:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":423,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4154:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":425,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4154:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":426,"nodeType":"ExpressionStatement","src":"4154:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":434,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4231:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":433,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4223:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":432,"name":"uint160","nodeType":"ElementaryTypeName","src":"4223:7:0","typeDescriptions":{}}},"id":435,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4223:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":431,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4215:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":430,"name":"address","nodeType":"ElementaryTypeName","src":"4215:7:0","typeDescriptions":{}}},"id":436,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4215:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":427,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4197:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":429,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4197:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":437,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4197:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":438,"nodeType":"ExpressionStatement","src":"4197:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":442,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4263:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":439,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4252:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":441,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4252:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":443,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4252:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":444,"nodeType":"ExpressionStatement","src":"4252:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":448,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4302:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":445,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4291:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4291:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":449,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4291:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":450,"nodeType":"ExpressionStatement","src":"4291:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":454,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4344:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":451,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4330:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":705,"src":"4330:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":455,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4330:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":456,"nodeType":"ExpressionStatement","src":"4330:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":457,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4371:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":459,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4371:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":460,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4371:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":461,"nodeType":"ExpressionStatement","src":"4371:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":465,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4410:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":462,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4399:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":464,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4399:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":466,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4399:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":467,"nodeType":"ExpressionStatement","src":"4399:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":471,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4451:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":468,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4439:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":470,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4439:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4439:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":473,"nodeType":"ExpressionStatement","src":"4439:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":481,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4512:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":480,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4504:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":479,"name":"uint160","nodeType":"ElementaryTypeName","src":"4504:7:0","typeDescriptions":{}}},"id":482,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4504:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":478,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4496:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":477,"name":"address","nodeType":"ElementaryTypeName","src":"4496:7:0","typeDescriptions":{}}},"id":483,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4496:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":474,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4478:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":476,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4478:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4478:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":485,"nodeType":"ExpressionStatement","src":"4478:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":489,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4544:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":486,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4531:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":488,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":678,"src":"4531:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":490,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4531:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":491,"nodeType":"ExpressionStatement","src":"4531:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":492,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4563:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":494,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4563:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4563:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":496,"nodeType":"ExpressionStatement","src":"4563:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":500,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4604:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":497,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4592:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":499,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4592:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4592:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":502,"nodeType":"ExpressionStatement","src":"4592:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":510,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4665:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4657:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":508,"name":"uint160","nodeType":"ElementaryTypeName","src":"4657:7:0","typeDescriptions":{}}},"id":511,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":507,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4649:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":506,"name":"address","nodeType":"ElementaryTypeName","src":"4649:7:0","typeDescriptions":{}}},"id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4649:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":503,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4636:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":505,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4636:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":513,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4636:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":514,"nodeType":"ExpressionStatement","src":"4636:40:0"},{"assignments":[517],"declarations":[{"constant":false,"id":517,"mutability":"mutable","name":"x","nameLocation":"4693:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4686:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":516,"nodeType":"UserDefinedTypeName","pathNode":{"id":515,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4686:6:0"},"referencedDeclaration":719,"src":"4686:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":523,"initialValue":{"arguments":[{"hexValue":"31323334","id":521,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4708:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":520,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4697:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":519,"nodeType":"UserDefinedTypeName","pathNode":{"id":518,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4701:6:0"},"referencedDeclaration":719,"src":"4701:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":522,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4697:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4686:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":529,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":525,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":517,"src":"4731:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":526,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4731:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4731:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":528,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4742:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4731:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":524,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4723:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":530,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4723:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":531,"nodeType":"ExpressionStatement","src":"4723:24:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":535,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4770:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":532,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4758:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":534,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4758:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":536,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4758:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":537,"nodeType":"ExpressionStatement","src":"4758:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":545,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4820:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":544,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4812:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":543,"name":"uint160","nodeType":"ElementaryTypeName","src":"4812:7:0","typeDescriptions":{}}},"id":546,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4812:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":542,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4804:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":541,"name":"address","nodeType":"ElementaryTypeName","src":"4804:7:0","typeDescriptions":{}}},"id":547,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4804:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":538,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4791:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":540,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4791:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4791:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":549,"nodeType":"ExpressionStatement","src":"4791:38:0"},{"assignments":[552],"declarations":[{"constant":false,"id":552,"mutability":"mutable","name":"y","nameLocation":"4846:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4839:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":551,"nodeType":"UserDefinedTypeName","pathNode":{"id":550,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4839:6:0"},"referencedDeclaration":719,"src":"4839:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":566,"initialValue":{"arguments":[{"hexValue":"31323334","id":564,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4889:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":555,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4850:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":554,"nodeType":"UserDefinedTypeName","pathNode":{"id":553,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4854:6:0"},"referencedDeclaration":719,"src":"4854:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":563,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4883:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":559,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4875:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":558,"name":"uint256","nodeType":"ElementaryTypeName","src":"4875:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4875:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":557,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4867:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":556,"name":"bytes32","nodeType":"ElementaryTypeName","src":"4867:7:0","typeDescriptions":{}}},"id":562,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4867:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"4850:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":565,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4850:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4839:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":568,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":552,"src":"4912:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":569,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4912:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":570,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4912:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":571,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4923:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4912:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":567,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4904:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4904:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"4904:24:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4950:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4938:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4938:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4938:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"4938:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5042:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5042:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":584,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5042:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":585,"nodeType":"ExpressionStatement","src":"5042:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5077:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":588,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5066:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":587,"nodeType":"UserDefinedTypeName","pathNode":{"id":586,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"5070:6:0"},"referencedDeclaration":719,"src":"5070:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":590,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5066:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":591,"nodeType":"ExpressionStatement","src":"5066:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":595,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5105:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":602,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5146:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":601,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5138:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":600,"name":"address","nodeType":"ElementaryTypeName","src":"5138:7:0","typeDescriptions":{}}},"id":603,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5138:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":598,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5126:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":599,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5126:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5126:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":597,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5118:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":596,"name":"uint256","nodeType":"ElementaryTypeName","src":"5118:7:0","typeDescriptions":{}}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5118:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":592,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5093:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":594,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"5093:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":606,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5093:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":607,"nodeType":"ExpressionStatement","src":"5093:61:0"}]},"documentation":{"id":379,"nodeType":"StructuredDocumentation","src":"3842:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"3912:12:0","parameters":{"id":380,"nodeType":"ParameterList","parameters":[],"src":"3924:2:0"},"returnParameters":{"id":381,"nodeType":"ParameterList","parameters":[],"src":"3934:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":633,"nodeType":"FunctionDefinition","src":"5256:143:0","nodes":[],"body":{"id":632,"nodeType":"Block","src":"5305:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":618,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":612,"src":"5327:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":615,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5315:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":617,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5315:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5315:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":620,"nodeType":"ExpressionStatement","src":"5315:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5352:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":627,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"5380:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":628,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"5380:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":626,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5372:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":625,"name":"address","nodeType":"ElementaryTypeName","src":"5372:7:0","typeDescriptions":{}}},"id":629,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5372:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":621,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5340:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":623,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"5340:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":630,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5340:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":631,"nodeType":"ExpressionStatement","src":"5340:52:0"}]},"documentation":{"id":610,"nodeType":"StructuredDocumentation","src":"5167:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"5265:5:0","parameters":{"id":613,"nodeType":"ParameterList","parameters":[{"constant":false,"id":612,"mutability":"mutable","name":"_v","nameLocation":"5287:2:0","nodeType":"VariableDeclaration","scope":633,"src":"5271:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":611,"name":"string","nodeType":"ElementaryTypeName","src":"5271:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5270:20:0"},"returnParameters":{"id":614,"nodeType":"ParameterList","parameters":[],"src":"5305:0:0"},"scope":706,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":648,"nodeType":"FunctionDefinition","src":"5405:95:0","nodes":[],"body":{"id":647,"nodeType":"Block","src":"5449:51:0","nodes":[],"statements":[{"expression":{"id":639,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5459:9:0","subExpression":{"id":638,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5459:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":640,"nodeType":"ExpressionStatement","src":"5459:9:0"},{"expression":{"arguments":[{"id":644,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":635,"src":"5490:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":641,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5478:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":643,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5478:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":645,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5478:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":646,"nodeType":"ExpressionStatement","src":"5478:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"5414:5:0","parameters":{"id":636,"nodeType":"ParameterList","parameters":[{"constant":false,"id":635,"mutability":"mutable","name":"_v","nameLocation":"5436:2:0","nodeType":"VariableDeclaration","scope":648,"src":"5420:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":634,"name":"string","nodeType":"ElementaryTypeName","src":"5420:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5419:20:0"},"returnParameters":{"id":637,"nodeType":"ParameterList","parameters":[],"src":"5449:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":663,"nodeType":"FunctionDefinition","src":"5506:95:0","nodes":[],"body":{"id":662,"nodeType":"Block","src":"5550:51:0","nodes":[],"statements":[{"expression":{"id":654,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5560:9:0","subExpression":{"id":653,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5560:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":655,"nodeType":"ExpressionStatement","src":"5560:9:0"},{"expression":{"arguments":[{"id":659,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":650,"src":"5591:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":656,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5579:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":658,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5579:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":660,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5579:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":661,"nodeType":"ExpressionStatement","src":"5579:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"5515:5:0","parameters":{"id":651,"nodeType":"ParameterList","parameters":[{"constant":false,"id":650,"mutability":"mutable","name":"_v","nameLocation":"5537:2:0","nodeType":"VariableDeclaration","scope":663,"src":"5521:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":649,"name":"string","nodeType":"ElementaryTypeName","src":"5521:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5520:20:0"},"returnParameters":{"id":652,"nodeType":"ParameterList","parameters":[],"src":"5550:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":678,"nodeType":"FunctionDefinition","src":"5607:98:0","nodes":[],"body":{"id":677,"nodeType":"Block","src":"5653:52:0","nodes":[],"statements":[{"expression":{"id":669,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5663:9:0","subExpression":{"id":668,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5663:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":670,"nodeType":"ExpressionStatement","src":"5663:9:0"},{"expression":{"arguments":[{"id":674,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":665,"src":"5695:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":671,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5682:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":673,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":693,"src":"5682:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":675,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5682:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":676,"nodeType":"ExpressionStatement","src":"5682:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"5616:7:0","parameters":{"id":666,"nodeType":"ParameterList","parameters":[{"constant":false,"id":665,"mutability":"mutable","name":"_v","nameLocation":"5640:2:0","nodeType":"VariableDeclaration","scope":678,"src":"5624:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":664,"name":"string","nodeType":"ElementaryTypeName","src":"5624:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5623:20:0"},"returnParameters":{"id":667,"nodeType":"ParameterList","parameters":[],"src":"5653:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":693,"nodeType":"FunctionDefinition","src":"5711:97:0","nodes":[],"body":{"id":692,"nodeType":"Block","src":"5757:51:0","nodes":[],"statements":[{"expression":{"id":684,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5767:9:0","subExpression":{"id":683,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5767:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":685,"nodeType":"ExpressionStatement","src":"5767:9:0"},{"expression":{"arguments":[{"id":689,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":680,"src":"5798:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":686,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5786:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":688,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5786:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":690,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5786:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":691,"nodeType":"ExpressionStatement","src":"5786:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"5720:7:0","parameters":{"id":681,"nodeType":"ParameterList","parameters":[{"constant":false,"id":680,"mutability":"mutable","name":"_v","nameLocation":"5744:2:0","nodeType":"VariableDeclaration","scope":693,"src":"5728:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":679,"name":"string","nodeType":"ElementaryTypeName","src":"5728:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5727:20:0"},"returnParameters":{"id":682,"nodeType":"ParameterList","parameters":[],"src":"5757:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":705,"nodeType":"FunctionDefinition","src":"5814:84:0","nodes":[],"body":{"id":704,"nodeType":"Block","src":"5866:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":701,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":695,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":698,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5876:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":700,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5876:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":702,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5876:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":703,"nodeType":"ExpressionStatement","src":"5876:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"5823:8:0","parameters":{"id":696,"nodeType":"ParameterList","parameters":[{"constant":false,"id":695,"mutability":"mutable","name":"_v","nameLocation":"5848:2:0","nodeType":"VariableDeclaration","scope":705,"src":"5832:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":694,"name":"string","nodeType":"ElementaryTypeName","src":"5832:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5831:20:0"},"returnParameters":{"id":697,"nodeType":"ParameterList","parameters":[],"src":"5866:0:0"},"scope":706,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[719],"contractKind":"contract","documentation":{"id":193,"nodeType":"StructuredDocumentation","src":"2415:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[706],"name":"ScriptExample","nameLocation":"2550:13:0","scope":720,"usedErrors":[]},{"id":719,"nodeType":"ContractDefinition","src":"5902:96:0","nodes":[{"id":708,"nodeType":"VariableDeclaration","src":"5924:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"5939:3:0","scope":719,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":707,"name":"uint256","nodeType":"ElementaryTypeName","src":"5924:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":718,"nodeType":"FunctionDefinition","src":"5949:47:0","nodes":[],"body":{"id":717,"nodeType":"Block","src":"5972:24:0","nodes":[],"statements":[{"expression":{"id":715,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":713,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":708,"src":"5982:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":714,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":710,"src":"5988:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"5982:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":716,"nodeType":"ExpressionStatement","src":"5982:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":711,"nodeType":"ParameterList","parameters":[{"constant":false,"id":710,"mutability":"mutable","name":"v","nameLocation":"5969:1:0","nodeType":"VariableDeclaration","scope":718,"src":"5961:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":709,"name":"uint256","nodeType":"ElementaryTypeName","src":"5961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"5960:11:0"},"returnParameters":{"id":712,"nodeType":"ParameterList","parameters":[],"src":"5972:0:0"},"scope":719,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[719],"name":"FooBar","nameLocation":"5911:6:0","scope":720,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file +{"abi":[{"type":"constructor","inputs":[{"name":"v","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"foo","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b506040516100b23803806100b283398101604081905261002f91610037565b600055610050565b60006020828403121561004957600080fd5b5051919050565b60548061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000a","sourceMap":"7037:96:0:-:0;;;7084:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;7117:3;:7;7037:96;;14:184:1;84:6;137:2;125:9;116:7;112:23;108:32;105:52;;;153:1;150;143:12;105:52;-1:-1:-1;176:16:1;;14:184;-1:-1:-1;14:184:1:o;:::-;7037:96:0;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000a","sourceMap":"7037:96:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7059:18;;;;;;;;;160:25:1;;;148:2;133:18;7059::0;;;;;;","linkReferences":{}},"methodIdentifiers":{"foo()":"c2985578"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"v\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"foo\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"FooBar\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28\",\"dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"uint256","name":"v","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"stateMutability":"view","type":"function","name":"foo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"FooBar"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da","urls":["bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28","dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[{"astId":788,"contract":"scripts/ScriptExample.s.sol:FooBar","label":"foo","offset":0,"slot":"0","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":969,"exportedSymbols":{"FooBar":[799],"ForkTester":[968],"ForkedContract":[852],"NonceGetter":[833],"ScriptExample":[786],"Vm":[83],"console":[220]},"nodeType":"SourceUnit","src":"32:8375:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":83,"nodeType":"ContractDefinition","src":"120:969:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":83,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":61,"nodeType":"FunctionDefinition","src":"739:108:0","nodes":[],"functionSelector":"3ebf73b4","implemented":false,"kind":"function","modifiers":[],"name":"getDeployedCode","nameLocation":"748:15:0","parameters":{"id":57,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56,"mutability":"mutable","name":"artifactPath","nameLocation":"780:12:0","nodeType":"VariableDeclaration","scope":61,"src":"764:28:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":55,"name":"string","nodeType":"ElementaryTypeName","src":"764:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"763:30:0"},"returnParameters":{"id":60,"nodeType":"ParameterList","parameters":[{"constant":false,"id":59,"mutability":"mutable","name":"runtimeBytecode","nameLocation":"830:15:0","nodeType":"VariableDeclaration","scope":61,"src":"817:28:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":58,"name":"bytes","nodeType":"ElementaryTypeName","src":"817:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"816:30:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":68,"nodeType":"FunctionDefinition","src":"852:74:0","nodes":[],"functionSelector":"b4d6c782","implemented":false,"kind":"function","modifiers":[],"name":"etch","nameLocation":"861:4:0","parameters":{"id":66,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"target","nameLocation":"874:6:0","nodeType":"VariableDeclaration","scope":68,"src":"866:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":62,"name":"address","nodeType":"ElementaryTypeName","src":"866:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":65,"mutability":"mutable","name":"newRuntimeBytecode","nameLocation":"897:18:0","nodeType":"VariableDeclaration","scope":68,"src":"882:33:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":64,"name":"bytes","nodeType":"ElementaryTypeName","src":"882:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"865:51:0"},"returnParameters":{"id":67,"nodeType":"ParameterList","parameters":[],"src":"925:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":73,"nodeType":"FunctionDefinition","src":"931:51:0","nodes":[],"functionSelector":"ea060291","implemented":false,"kind":"function","modifiers":[],"name":"allowCheatcodes","nameLocation":"940:15:0","parameters":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"account","nameLocation":"964:7:0","nodeType":"VariableDeclaration","scope":73,"src":"956:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":69,"name":"address","nodeType":"ElementaryTypeName","src":"956:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"955:17:0"},"returnParameters":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"981:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":82,"nodeType":"FunctionDefinition","src":"987:100:0","nodes":[],"functionSelector":"71ee464d","implemented":false,"kind":"function","modifiers":[],"name":"createSelectFork","nameLocation":"996:16:0","parameters":{"id":78,"nodeType":"ParameterList","parameters":[{"constant":false,"id":75,"mutability":"mutable","name":"forkName","nameLocation":"1029:8:0","nodeType":"VariableDeclaration","scope":82,"src":"1013:24:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":74,"name":"string","nodeType":"ElementaryTypeName","src":"1013:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":77,"mutability":"mutable","name":"blockNumber","nameLocation":"1047:11:0","nodeType":"VariableDeclaration","scope":82,"src":"1039:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":76,"name":"uint256","nodeType":"ElementaryTypeName","src":"1039:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1012:47:0"},"returnParameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":82,"src":"1078:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":79,"name":"uint256","nodeType":"ElementaryTypeName","src":"1078:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1077:9:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[83],"name":"Vm","nameLocation":"130:2:0","scope":969,"usedErrors":[]},{"id":220,"nodeType":"ContractDefinition","src":"1144:1851:0","nodes":[{"id":89,"nodeType":"VariableDeclaration","src":"1166:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"1183:15:0","scope":220,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":84,"name":"address","nodeType":"ElementaryTypeName","src":"1166:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":87,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1209:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":86,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":85,"name":"address","nodeType":"ElementaryTypeName","src":"1201:7:0","typeDescriptions":{}}},"id":88,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1201:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1259:235:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1432:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1451:37:0","statements":[{"nodeType":"YulAssignment","src":"1465:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1474:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1465:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":95,"isOffset":false,"isSlot":false,"src":"1474:4:0","valueSize":1},{"declaration":102,"isOffset":false,"isSlot":false,"src":"1465:5:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1442:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"1268:25:0","parameters":{"id":96,"nodeType":"ParameterList","parameters":[{"constant":false,"id":95,"mutability":"mutable","name":"fnIn","nameLocation":"1331:4:0","nodeType":"VariableDeclaration","scope":106,"src":"1294:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":94,"nodeType":"FunctionTypeName","parameterTypes":{"id":92,"nodeType":"ParameterList","parameters":[{"constant":false,"id":91,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":94,"src":"1303:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":90,"name":"bytes","nodeType":"ElementaryTypeName","src":"1303:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1302:14:0"},"returnParameterTypes":{"id":93,"nodeType":"ParameterList","parameters":[],"src":"1331:0:0"},"src":"1294:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"1293:43:0"},"returnParameters":{"id":103,"nodeType":"ParameterList","parameters":[{"constant":false,"id":102,"mutability":"mutable","name":"fnOut","nameLocation":"1421:5:0","nodeType":"VariableDeclaration","scope":106,"src":"1384:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":101,"nodeType":"FunctionTypeName","parameterTypes":{"id":99,"nodeType":"ParameterList","parameters":[{"constant":false,"id":98,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":101,"src":"1393:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":97,"name":"bytes","nodeType":"ElementaryTypeName","src":"1393:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1392:14:0"},"returnParameterTypes":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"1421:0:0"},"src":"1384:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1383:44:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":118,"nodeType":"FunctionDefinition","src":"1500:133:0","nodes":[],"body":{"id":117,"nodeType":"Block","src":"1561:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":114,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1618:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":112,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":134,"src":"1597:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":111,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1571:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":113,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":115,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":116,"nodeType":"ExpressionStatement","src":"1571:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1509:15:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"payload","nameLocation":"1538:7:0","nodeType":"VariableDeclaration","scope":118,"src":"1525:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":107,"name":"bytes","nodeType":"ElementaryTypeName","src":"1525:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1524:22:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1561:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":134,"nodeType":"FunctionDefinition","src":"1639:380:0","nodes":[],"body":{"id":133,"nodeType":"Block","src":"1703:316:0","nodes":[],"statements":[{"assignments":[124],"declarations":[{"constant":false,"id":124,"mutability":"mutable","name":"payloadLength","nameLocation":"1721:13:0","nodeType":"VariableDeclaration","scope":133,"src":"1713:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":123,"name":"uint256","nodeType":"ElementaryTypeName","src":"1713:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":127,"initialValue":{"expression":{"id":125,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":120,"src":"1737:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":126,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1737:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1713:38:0"},{"assignments":[129],"declarations":[{"constant":false,"id":129,"mutability":"mutable","name":"consoleAddress","nameLocation":"1769:14:0","nodeType":"VariableDeclaration","scope":133,"src":"1761:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":128,"name":"address","nodeType":"ElementaryTypeName","src":"1761:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":131,"initialValue":{"id":130,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":89,"src":"1786:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1761:40:0"},{"AST":{"nodeType":"YulBlock","src":"1863:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1877:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1901:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1910:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1897:3:0"},"nodeType":"YulFunctionCall","src":"1897:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1881:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1926:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1946:3:0"},"nodeType":"YulFunctionCall","src":"1946:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1953:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1969:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1983:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1998:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"2001:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1935:10:0"},"nodeType":"YulFunctionCall","src":"1935:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1930:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":129,"isOffset":false,"isSlot":false,"src":"1953:14:0","valueSize":1},{"declaration":120,"isOffset":false,"isSlot":false,"src":"1901:7:0","valueSize":1},{"declaration":124,"isOffset":false,"isSlot":false,"src":"1983:13:0","valueSize":1}],"id":132,"nodeType":"InlineAssembly","src":"1854:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1648:19:0","parameters":{"id":121,"nodeType":"ParameterList","parameters":[{"constant":false,"id":120,"mutability":"mutable","name":"payload","nameLocation":"1681:7:0","nodeType":"VariableDeclaration","scope":134,"src":"1668:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":119,"name":"bytes","nodeType":"ElementaryTypeName","src":"1668:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1667:22:0"},"returnParameters":{"id":122,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":220,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":148,"nodeType":"FunctionDefinition","src":"2025:164:0","nodes":[],"body":{"id":147,"nodeType":"Block","src":"2070:119:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":142,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2120:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":143,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":136,"src":"2135:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":140,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2096:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":141,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2096: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":144,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2096:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":139,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2080:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":145,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2080:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":146,"nodeType":"ExpressionStatement","src":"2080:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2034:3:0","parameters":{"id":137,"nodeType":"ParameterList","parameters":[{"constant":false,"id":136,"mutability":"mutable","name":"p0","nameLocation":"2052:2:0","nodeType":"VariableDeclaration","scope":148,"src":"2038:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":135,"name":"string","nodeType":"ElementaryTypeName","src":"2038:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2037:18:0"},"returnParameters":{"id":138,"nodeType":"ParameterList","parameters":[],"src":"2070:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":165,"nodeType":"FunctionDefinition","src":"2195:182:0","nodes":[],"body":{"id":164,"nodeType":"Block","src":"2249:128:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":158,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2299:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":159,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":150,"src":"2319:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":160,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":152,"src":"2323:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":156,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2275:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":157,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2275: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":161,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2275:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":155,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2259:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":162,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2259:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":163,"nodeType":"ExpressionStatement","src":"2259:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2204:3:0","parameters":{"id":153,"nodeType":"ParameterList","parameters":[{"constant":false,"id":150,"mutability":"mutable","name":"p0","nameLocation":"2222:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2208:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":149,"name":"string","nodeType":"ElementaryTypeName","src":"2208:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":152,"mutability":"mutable","name":"p1","nameLocation":"2231:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2226:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":151,"name":"bool","nodeType":"ElementaryTypeName","src":"2226:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"2207:27:0"},"returnParameters":{"id":154,"nodeType":"ParameterList","parameters":[],"src":"2249:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":182,"nodeType":"FunctionDefinition","src":"2383:188:0","nodes":[],"body":{"id":181,"nodeType":"Block","src":"2440:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":175,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2490:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":176,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":167,"src":"2513:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":177,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":169,"src":"2517:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":173,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2466:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":174,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2466: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":178,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2466:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":172,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2450:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":179,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2450:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":180,"nodeType":"ExpressionStatement","src":"2450:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2392:3:0","parameters":{"id":170,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"mutability":"mutable","name":"p0","nameLocation":"2410:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2396:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":166,"name":"string","nodeType":"ElementaryTypeName","src":"2396:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":169,"mutability":"mutable","name":"p1","nameLocation":"2422:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2414:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":168,"name":"uint256","nodeType":"ElementaryTypeName","src":"2414:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2395:30:0"},"returnParameters":{"id":171,"nodeType":"ParameterList","parameters":[],"src":"2440:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":199,"nodeType":"FunctionDefinition","src":"2577:188:0","nodes":[],"body":{"id":198,"nodeType":"Block","src":"2634:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":192,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2684:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":193,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":184,"src":"2707:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":194,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"2711:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":190,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2660:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":191,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2660: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":195,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2660:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":189,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2644:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":196,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2644:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":197,"nodeType":"ExpressionStatement","src":"2644:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2586:3:0","parameters":{"id":187,"nodeType":"ParameterList","parameters":[{"constant":false,"id":184,"mutability":"mutable","name":"p0","nameLocation":"2604:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2590:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":183,"name":"string","nodeType":"ElementaryTypeName","src":"2590:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":186,"mutability":"mutable","name":"p1","nameLocation":"2616:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2608:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":185,"name":"address","nodeType":"ElementaryTypeName","src":"2608:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2589:30:0"},"returnParameters":{"id":188,"nodeType":"ParameterList","parameters":[],"src":"2634:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":219,"nodeType":"FunctionDefinition","src":"2771:222:0","nodes":[],"body":{"id":218,"nodeType":"Block","src":"2852:141:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":211,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2902:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":212,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":201,"src":"2931:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":213,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":203,"src":"2935:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":214,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":205,"src":"2939:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":209,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2878:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":210,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2878: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":215,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2878:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":208,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2862:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":216,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2862:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":217,"nodeType":"ExpressionStatement","src":"2862:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2780:3:0","parameters":{"id":206,"nodeType":"ParameterList","parameters":[{"constant":false,"id":201,"mutability":"mutable","name":"p0","nameLocation":"2798:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2784:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":200,"name":"string","nodeType":"ElementaryTypeName","src":"2784:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":203,"mutability":"mutable","name":"p1","nameLocation":"2816:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2802:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":202,"name":"string","nodeType":"ElementaryTypeName","src":"2802:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":205,"mutability":"mutable","name":"p2","nameLocation":"2834:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2820:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":204,"name":"string","nodeType":"ElementaryTypeName","src":"2820:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2783:54:0"},"returnParameters":{"id":207,"nodeType":"ParameterList","parameters":[],"src":"2852:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[220],"name":"console","nameLocation":"1152:7:0","scope":969,"usedErrors":[]},{"id":786,"nodeType":"ContractDefinition","src":"3123:3912:0","nodes":[{"id":235,"nodeType":"VariableDeclaration","src":"3152:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"3178:10:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":222,"name":"address","nodeType":"ElementaryTypeName","src":"3152:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3225:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":229,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"3215:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":231,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3215:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":228,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3207:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":227,"name":"uint256","nodeType":"ElementaryTypeName","src":"3207:7:0","typeDescriptions":{}}},"id":232,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3207:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":226,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3199:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":225,"name":"uint160","nodeType":"ElementaryTypeName","src":"3199:7:0","typeDescriptions":{}}},"id":233,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3199:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":224,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3191:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":223,"name":"address","nodeType":"ElementaryTypeName","src":"3191:7:0","typeDescriptions":{}}},"id":234,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3191:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":241,"nodeType":"VariableDeclaration","src":"3252:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"3273:2:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":237,"nodeType":"UserDefinedTypeName","pathNode":{"id":236,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"3252:2:0"},"referencedDeclaration":83,"src":"3252:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":239,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":235,"src":"3281:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":238,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"3278:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":240,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3278:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":243,"nodeType":"VariableDeclaration","src":"3356:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"3371:7:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":242,"name":"uint256","nodeType":"ElementaryTypeName","src":"3356:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":456,"nodeType":"FunctionDefinition","src":"3468:1428:0","nodes":[],"body":{"id":455,"nodeType":"Block","src":"3490:1406:0","nodes":[],"statements":[{"assignments":[248],"declarations":[{"constant":false,"id":248,"mutability":"mutable","name":"x","nameLocation":"3505:1:0","nodeType":"VariableDeclaration","scope":455,"src":"3500:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":247,"name":"bool","nodeType":"ElementaryTypeName","src":"3500:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":254,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":251,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3518:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":252,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"3534:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":249,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3509:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":250,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"3509:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"3500:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":258,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3562:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":259,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":248,"src":"3585:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":255,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3550:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":257,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":165,"src":"3550:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":260,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3550:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":261,"nodeType":"ExpressionStatement","src":"3550:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":265,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3610:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":268,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3635:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":267,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3627:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":266,"name":"address","nodeType":"ElementaryTypeName","src":"3627:7:0","typeDescriptions":{}}},"id":269,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3627:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":262,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3598:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3598:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3598:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":271,"nodeType":"ExpressionStatement","src":"3598:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":275,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3663:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":280,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3701:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":279,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3693:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":278,"name":"address","nodeType":"ElementaryTypeName","src":"3693:7:0","typeDescriptions":{}}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3693:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":276,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3681:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3681:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":282,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":272,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3651:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":274,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3651:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":283,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3651:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":284,"nodeType":"ExpressionStatement","src":"3651:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":288,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3730:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":291,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3753:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3753:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":290,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3745:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":289,"name":"address","nodeType":"ElementaryTypeName","src":"3745:7:0","typeDescriptions":{}}},"id":293,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3745:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":285,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3718:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3718:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3718:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":295,"nodeType":"ExpressionStatement","src":"3718:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":299,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3787:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":304,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3823:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":305,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3823:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":303,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3815:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":302,"name":"address","nodeType":"ElementaryTypeName","src":"3815:7:0","typeDescriptions":{}}},"id":306,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3815:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":300,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3803:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3803:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3803:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":296,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3775:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":298,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3775:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":308,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3775:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":309,"nodeType":"ExpressionStatement","src":"3775:61:0"},{"assignments":[311],"declarations":[{"constant":false,"id":311,"mutability":"mutable","name":"json","nameLocation":"3861:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3847:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":310,"name":"string","nodeType":"ElementaryTypeName","src":"3847:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":313,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3868:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3847:55:0"},{"assignments":[318],"declarations":[{"constant":false,"id":318,"mutability":"mutable","name":"keys","nameLocation":"3928:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3912:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":316,"name":"string","nodeType":"ElementaryTypeName","src":"3912:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":317,"nodeType":"ArrayTypeName","src":"3912:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":324,"initialValue":{"arguments":[{"id":321,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":311,"src":"3952:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3958:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":319,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3935:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":320,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3935:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":323,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3935:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3912:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":328,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3995:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":329,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4003:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":331,"indexExpression":{"hexValue":"30","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4008:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4003:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":332,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4012:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":334,"indexExpression":{"hexValue":"31","id":333,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4017:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4012:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":325,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3983:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":327,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":219,"src":"3983:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3983:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":336,"nodeType":"ExpressionStatement","src":"3983:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":340,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4042:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":337,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4031:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":339,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4031:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4031:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":342,"nodeType":"ExpressionStatement","src":"4031:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":350,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4098:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4090:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":348,"name":"uint160","nodeType":"ElementaryTypeName","src":"4090:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4090:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":347,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4082:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":346,"name":"address","nodeType":"ElementaryTypeName","src":"4082:7:0","typeDescriptions":{}}},"id":352,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4082:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":343,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4068:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":345,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"4068:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":353,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4068:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":354,"nodeType":"ExpressionStatement","src":"4068:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":358,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4126:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":355,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4115:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":357,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4115:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":359,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4115:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":360,"nodeType":"ExpressionStatement","src":"4115:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":364,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4163:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":367,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4198:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":368,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"4198:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":366,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4190:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":365,"name":"address","nodeType":"ElementaryTypeName","src":"4190:7:0","typeDescriptions":{}}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4190:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":361,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4151:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4151:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":370,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4151:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":371,"nodeType":"ExpressionStatement","src":"4151:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":375,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4232:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":378,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4270:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":377,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4262:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":376,"name":"address","nodeType":"ElementaryTypeName","src":"4262:7:0","typeDescriptions":{}}},"id":379,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4262:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":372,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4220:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":374,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4220:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":380,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4220:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":381,"nodeType":"ExpressionStatement","src":"4220:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4297:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":382,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4286:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4286:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":386,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4286:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":387,"nodeType":"ExpressionStatement","src":"4286:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4322:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":390,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"4322:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":391,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4322:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":392,"nodeType":"ExpressionStatement","src":"4322:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":396,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4357:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":393,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4346:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4346:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":397,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4346:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":398,"nodeType":"ExpressionStatement","src":"4346:33:0"},{"assignments":[400],"declarations":[{"constant":false,"id":400,"mutability":"mutable","name":"tmpNonceGetter","nameLocation":"4480:14:0","nodeType":"VariableDeclaration","scope":455,"src":"4472:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":399,"name":"address","nodeType":"ElementaryTypeName","src":"4472:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":413,"initialValue":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"74656d70206e6f6e6365207465737420676574746572","id":408,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4531:24:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""},"value":"temp nonce test getter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""}],"id":407,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"4521:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":409,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4521:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":406,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4513:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":405,"name":"uint256","nodeType":"ElementaryTypeName","src":"4513:7:0","typeDescriptions":{}}},"id":410,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4513:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":404,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4505:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":403,"name":"uint160","nodeType":"ElementaryTypeName","src":"4505:7:0","typeDescriptions":{}}},"id":411,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4505:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":402,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4497:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":401,"name":"address","nodeType":"ElementaryTypeName","src":"4497:7:0","typeDescriptions":{}}},"id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4497:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"4472:87:0"},{"expression":{"arguments":[{"id":417,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4577:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"hexValue":"5363726970744578616d706c652e732e736f6c3a4e6f6e6365476574746572","id":420,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4612:33:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""},"value":"ScriptExample.s.sol:NonceGetter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""}],"expression":{"id":418,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4593:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getDeployedCode","nodeType":"MemberAccess","referencedDeclaration":61,"src":"4593:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) view external returns (bytes memory)"}},"id":421,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4593:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":414,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4569:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":416,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"etch","nodeType":"MemberAccess","referencedDeclaration":68,"src":"4569:7:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory) external"}},"id":422,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4569:78:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":423,"nodeType":"ExpressionStatement","src":"4569:78:0"},{"expression":{"arguments":[{"id":427,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4676:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":424,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4657:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":426,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"allowCheatcodes","nodeType":"MemberAccess","referencedDeclaration":73,"src":"4657:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":428,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":429,"nodeType":"ExpressionStatement","src":"4657:34:0"},{"assignments":[431],"declarations":[{"constant":false,"id":431,"mutability":"mutable","name":"v","nameLocation":"4709:1:0","nodeType":"VariableDeclaration","scope":455,"src":"4701:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":430,"name":"uint256","nodeType":"ElementaryTypeName","src":"4701:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":441,"initialValue":{"arguments":[{"arguments":[{"id":438,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4758:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":437,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4750:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":436,"name":"address","nodeType":"ElementaryTypeName","src":"4750:7:0","typeDescriptions":{}}},"id":439,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4750:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"arguments":[{"id":433,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4725:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":432,"name":"NonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":833,"src":"4713:11:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_NonceGetter_$833_$","typeString":"type(contract NonceGetter)"}},"id":434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_NonceGetter_$833","typeString":"contract NonceGetter"}},"id":435,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":832,"src":"4713:36:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view external returns (uint256)"}},"id":440,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"4701:63:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683a","id":445,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4786:68:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},"value":"nonce from nonce getter, no explicit access required with vm.etch:"},{"id":446,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":431,"src":"4856:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":442,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4774:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":444,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"4774:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4774:84:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":448,"nodeType":"ExpressionStatement","src":"4774:84:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":452,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4881:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":449,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4869:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":451,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"4869:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4869:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":454,"nodeType":"ExpressionStatement","src":"4869:20:0"}]},"documentation":{"id":244,"nodeType":"StructuredDocumentation","src":"3385:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"3477:3:0","parameters":{"id":245,"nodeType":"ParameterList","parameters":[],"src":"3480:2:0"},"returnParameters":{"id":246,"nodeType":"ParameterList","parameters":[],"src":"3490:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":689,"nodeType":"FunctionDefinition","src":"4963:1333:0","nodes":[],"body":{"id":688,"nodeType":"Block","src":"4994:1302:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":463,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5016:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":470,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5059:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":469,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5051:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":468,"name":"address","nodeType":"ElementaryTypeName","src":"5051:7:0","typeDescriptions":{}}},"id":471,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5051:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":466,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5039:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":467,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5039:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5039:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":465,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5031:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":464,"name":"uint256","nodeType":"ElementaryTypeName","src":"5031:7:0","typeDescriptions":{}}},"id":473,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5031:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":460,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5004:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":462,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"5004:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":474,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5004:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":475,"nodeType":"ExpressionStatement","src":"5004:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":479,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5090:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":476,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5078:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":478,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5078:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":480,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5078:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":481,"nodeType":"ExpressionStatement","src":"5078:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":482,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5117:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5117:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":485,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5117:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":486,"nodeType":"ExpressionStatement","src":"5117:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":490,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5152:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":487,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5141:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":489,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5141:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":491,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5141:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":492,"nodeType":"ExpressionStatement","src":"5141:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":496,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5188:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":493,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5177:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5177:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":497,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5177:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":498,"nodeType":"ExpressionStatement","src":"5177:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":502,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5226:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":499,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5214:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5214:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":503,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5214:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":504,"nodeType":"ExpressionStatement","src":"5214:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5291:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":511,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5283:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":510,"name":"uint160","nodeType":"ElementaryTypeName","src":"5283:7:0","typeDescriptions":{}}},"id":513,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5283:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5275:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":508,"name":"address","nodeType":"ElementaryTypeName","src":"5275:7:0","typeDescriptions":{}}},"id":514,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5275:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":505,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5257:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":507,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5257:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":515,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5257:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":516,"nodeType":"ExpressionStatement","src":"5257:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":520,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5323:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":517,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5312:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":519,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5312:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":521,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5312:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":522,"nodeType":"ExpressionStatement","src":"5312:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":526,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5362:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":523,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5351:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":525,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5351:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5351:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":528,"nodeType":"ExpressionStatement","src":"5351:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":532,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5404:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":529,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5390:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":531,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":785,"src":"5390:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":533,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5390:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":534,"nodeType":"ExpressionStatement","src":"5390:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":535,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5431:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":537,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5431:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":538,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5431:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":539,"nodeType":"ExpressionStatement","src":"5431:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":543,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5470:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":540,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5459:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":542,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5459:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5459:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":545,"nodeType":"ExpressionStatement","src":"5459:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":549,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5511:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":546,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5499:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5499:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":550,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5499:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":551,"nodeType":"ExpressionStatement","src":"5499:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":559,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5572:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":558,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5564:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":557,"name":"uint160","nodeType":"ElementaryTypeName","src":"5564:7:0","typeDescriptions":{}}},"id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5564:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":556,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5556:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":555,"name":"address","nodeType":"ElementaryTypeName","src":"5556:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5556:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":552,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5538:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":554,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5538:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5538:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":563,"nodeType":"ExpressionStatement","src":"5538:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":567,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5604:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":564,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5591:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":758,"src":"5591:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":568,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5591:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":569,"nodeType":"ExpressionStatement","src":"5591:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":570,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5623:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5623:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5623:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"5623:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5664:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5652:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5652:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5652:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"5652:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":588,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5725:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":587,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5717:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":586,"name":"uint160","nodeType":"ElementaryTypeName","src":"5717:7:0","typeDescriptions":{}}},"id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5717:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":585,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5709:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":584,"name":"address","nodeType":"ElementaryTypeName","src":"5709:7:0","typeDescriptions":{}}},"id":590,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5709:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5696:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5696:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":591,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5696:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":592,"nodeType":"ExpressionStatement","src":"5696:40:0"},{"assignments":[595],"declarations":[{"constant":false,"id":595,"mutability":"mutable","name":"x","nameLocation":"5753:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5746:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":594,"nodeType":"UserDefinedTypeName","pathNode":{"id":593,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5746:6:0"},"referencedDeclaration":799,"src":"5746:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":601,"initialValue":{"arguments":[{"hexValue":"31323334","id":599,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5768:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":598,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5757:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":597,"nodeType":"UserDefinedTypeName","pathNode":{"id":596,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5761:6:0"},"referencedDeclaration":799,"src":"5761:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":600,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5757:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5746:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":607,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":603,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":595,"src":"5791:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"5791:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5791:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":606,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5802:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"5791:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323334","id":608,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5808:35:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""},"value":"FooBar: foo in create is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""}],"id":602,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"5783:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":609,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5783:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":610,"nodeType":"ExpressionStatement","src":"5783:61:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":614,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5867:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":611,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5855:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":613,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5855:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":615,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5855:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":616,"nodeType":"ExpressionStatement","src":"5855:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5917:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":623,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5909:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":622,"name":"uint160","nodeType":"ElementaryTypeName","src":"5909:7:0","typeDescriptions":{}}},"id":625,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5909:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":621,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5901:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":620,"name":"address","nodeType":"ElementaryTypeName","src":"5901:7:0","typeDescriptions":{}}},"id":626,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5901:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":617,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5888:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":627,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5888:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":628,"nodeType":"ExpressionStatement","src":"5888:38:0"},{"assignments":[631],"declarations":[{"constant":false,"id":631,"mutability":"mutable","name":"y","nameLocation":"5943:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5936:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":630,"nodeType":"UserDefinedTypeName","pathNode":{"id":629,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5936:6:0"},"referencedDeclaration":799,"src":"5936:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":645,"initialValue":{"arguments":[{"hexValue":"31323334","id":643,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5986:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":634,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5947:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":633,"nodeType":"UserDefinedTypeName","pathNode":{"id":632,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5951:6:0"},"referencedDeclaration":799,"src":"5951:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":642,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":639,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5980:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":638,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5972:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":637,"name":"uint256","nodeType":"ElementaryTypeName","src":"5972:7:0","typeDescriptions":{}}},"id":640,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5972:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":636,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5964:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":635,"name":"bytes32","nodeType":"ElementaryTypeName","src":"5964:7:0","typeDescriptions":{}}},"id":641,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5964:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"5947:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":644,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5947:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5936:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":651,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":647,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":631,"src":"6009:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":648,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"6009:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":649,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6009:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":650,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6020:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"6009:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e2063726561746532206973206e6f742031323334","id":652,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6026:36:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""},"value":"FooBar: foo in create2 is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""}],"id":646,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"6001:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6001:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":654,"nodeType":"ExpressionStatement","src":"6001:62:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":658,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6085:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":655,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6073:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":657,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6073:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":659,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6073:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":660,"nodeType":"ExpressionStatement","src":"6073:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":661,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6177:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"6177:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":664,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6177:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":665,"nodeType":"ExpressionStatement","src":"6177:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":669,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6212:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":668,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"6201:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":667,"nodeType":"UserDefinedTypeName","pathNode":{"id":666,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"6205:6:0"},"referencedDeclaration":799,"src":"6205:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":670,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6201:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":671,"nodeType":"ExpressionStatement","src":"6201:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":675,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6240:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":682,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6281:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":681,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6273:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":680,"name":"address","nodeType":"ElementaryTypeName","src":"6273:7:0","typeDescriptions":{}}},"id":683,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6273:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":678,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6261:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"6261:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":684,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6261:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":677,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6253:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":676,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:0","typeDescriptions":{}}},"id":685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6253:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":672,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6228:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":674,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"6228:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":686,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6228:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":687,"nodeType":"ExpressionStatement","src":"6228:61:0"}]},"documentation":{"id":457,"nodeType":"StructuredDocumentation","src":"4902:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"4972:12:0","parameters":{"id":458,"nodeType":"ParameterList","parameters":[],"src":"4984:2:0"},"returnParameters":{"id":459,"nodeType":"ParameterList","parameters":[],"src":"4994:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":713,"nodeType":"FunctionDefinition","src":"6391:143:0","nodes":[],"body":{"id":712,"nodeType":"Block","src":"6440:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":698,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":692,"src":"6462:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":695,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6450:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":697,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6450:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":699,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6450:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":700,"nodeType":"ExpressionStatement","src":"6450:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6487:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":707,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"6515:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":708,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"6515:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":706,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6507:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":705,"name":"address","nodeType":"ElementaryTypeName","src":"6507:7:0","typeDescriptions":{}}},"id":709,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6507:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":701,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6475:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"6475:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":710,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6475:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":711,"nodeType":"ExpressionStatement","src":"6475:52:0"}]},"documentation":{"id":690,"nodeType":"StructuredDocumentation","src":"6302:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"6400:5:0","parameters":{"id":693,"nodeType":"ParameterList","parameters":[{"constant":false,"id":692,"mutability":"mutable","name":"_v","nameLocation":"6422:2:0","nodeType":"VariableDeclaration","scope":713,"src":"6406:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":691,"name":"string","nodeType":"ElementaryTypeName","src":"6406:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6405:20:0"},"returnParameters":{"id":694,"nodeType":"ParameterList","parameters":[],"src":"6440:0:0"},"scope":786,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":728,"nodeType":"FunctionDefinition","src":"6540:95:0","nodes":[],"body":{"id":727,"nodeType":"Block","src":"6584:51:0","nodes":[],"statements":[{"expression":{"id":719,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6594:9:0","subExpression":{"id":718,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6594:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":720,"nodeType":"ExpressionStatement","src":"6594:9:0"},{"expression":{"arguments":[{"id":724,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":715,"src":"6625:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":721,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6613:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":723,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6613:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":725,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6613:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":726,"nodeType":"ExpressionStatement","src":"6613:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"6549:5:0","parameters":{"id":716,"nodeType":"ParameterList","parameters":[{"constant":false,"id":715,"mutability":"mutable","name":"_v","nameLocation":"6571:2:0","nodeType":"VariableDeclaration","scope":728,"src":"6555:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":714,"name":"string","nodeType":"ElementaryTypeName","src":"6555:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6554:20:0"},"returnParameters":{"id":717,"nodeType":"ParameterList","parameters":[],"src":"6584:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":743,"nodeType":"FunctionDefinition","src":"6641:95:0","nodes":[],"body":{"id":742,"nodeType":"Block","src":"6685:51:0","nodes":[],"statements":[{"expression":{"id":734,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6695:9:0","subExpression":{"id":733,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6695:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":735,"nodeType":"ExpressionStatement","src":"6695:9:0"},{"expression":{"arguments":[{"id":739,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":730,"src":"6726:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":736,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6714:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":738,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6714:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":740,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6714:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":741,"nodeType":"ExpressionStatement","src":"6714:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"6650:5:0","parameters":{"id":731,"nodeType":"ParameterList","parameters":[{"constant":false,"id":730,"mutability":"mutable","name":"_v","nameLocation":"6672:2:0","nodeType":"VariableDeclaration","scope":743,"src":"6656:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":729,"name":"string","nodeType":"ElementaryTypeName","src":"6656:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6655:20:0"},"returnParameters":{"id":732,"nodeType":"ParameterList","parameters":[],"src":"6685:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":758,"nodeType":"FunctionDefinition","src":"6742:98:0","nodes":[],"body":{"id":757,"nodeType":"Block","src":"6788:52:0","nodes":[],"statements":[{"expression":{"id":749,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6798:9:0","subExpression":{"id":748,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6798:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":750,"nodeType":"ExpressionStatement","src":"6798:9:0"},{"expression":{"arguments":[{"id":754,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":745,"src":"6830:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":751,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6817:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":753,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":773,"src":"6817:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":755,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6817:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":756,"nodeType":"ExpressionStatement","src":"6817:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"6751:7:0","parameters":{"id":746,"nodeType":"ParameterList","parameters":[{"constant":false,"id":745,"mutability":"mutable","name":"_v","nameLocation":"6775:2:0","nodeType":"VariableDeclaration","scope":758,"src":"6759:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":744,"name":"string","nodeType":"ElementaryTypeName","src":"6759:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6758:20:0"},"returnParameters":{"id":747,"nodeType":"ParameterList","parameters":[],"src":"6788:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":773,"nodeType":"FunctionDefinition","src":"6846:97:0","nodes":[],"body":{"id":772,"nodeType":"Block","src":"6892:51:0","nodes":[],"statements":[{"expression":{"id":764,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6902:9:0","subExpression":{"id":763,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6902:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":765,"nodeType":"ExpressionStatement","src":"6902:9:0"},{"expression":{"arguments":[{"id":769,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":760,"src":"6933:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":766,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6921:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":768,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6921:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":770,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6921:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":771,"nodeType":"ExpressionStatement","src":"6921:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"6855:7:0","parameters":{"id":761,"nodeType":"ParameterList","parameters":[{"constant":false,"id":760,"mutability":"mutable","name":"_v","nameLocation":"6879:2:0","nodeType":"VariableDeclaration","scope":773,"src":"6863:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":759,"name":"string","nodeType":"ElementaryTypeName","src":"6863:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6862:20:0"},"returnParameters":{"id":762,"nodeType":"ParameterList","parameters":[],"src":"6892:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":785,"nodeType":"FunctionDefinition","src":"6949:84:0","nodes":[],"body":{"id":784,"nodeType":"Block","src":"7001:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":781,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":775,"src":"7023:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":778,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"7011:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":780,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"7011:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":782,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7011:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":783,"nodeType":"ExpressionStatement","src":"7011:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"6958:8:0","parameters":{"id":776,"nodeType":"ParameterList","parameters":[{"constant":false,"id":775,"mutability":"mutable","name":"_v","nameLocation":"6983:2:0","nodeType":"VariableDeclaration","scope":785,"src":"6967:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":774,"name":"string","nodeType":"ElementaryTypeName","src":"6967:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6966:20:0"},"returnParameters":{"id":777,"nodeType":"ParameterList","parameters":[],"src":"7001:0:0"},"scope":786,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[799],"contractKind":"contract","documentation":{"id":221,"nodeType":"StructuredDocumentation","src":"2997:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[786],"name":"ScriptExample","nameLocation":"3132:13:0","scope":969,"usedErrors":[]},{"id":799,"nodeType":"ContractDefinition","src":"7037:96:0","nodes":[{"id":788,"nodeType":"VariableDeclaration","src":"7059:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"7074:3:0","scope":799,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":787,"name":"uint256","nodeType":"ElementaryTypeName","src":"7059:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":798,"nodeType":"FunctionDefinition","src":"7084:47:0","nodes":[],"body":{"id":797,"nodeType":"Block","src":"7107:24:0","nodes":[],"statements":[{"expression":{"id":795,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":793,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":788,"src":"7117:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":794,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":790,"src":"7123:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"7117:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":796,"nodeType":"ExpressionStatement","src":"7117:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":791,"nodeType":"ParameterList","parameters":[{"constant":false,"id":790,"mutability":"mutable","name":"v","nameLocation":"7104:1:0","nodeType":"VariableDeclaration","scope":798,"src":"7096:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":789,"name":"uint256","nodeType":"ElementaryTypeName","src":"7096:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7095:11:0"},"returnParameters":{"id":792,"nodeType":"ParameterList","parameters":[],"src":"7107:0:0"},"scope":799,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[799],"name":"FooBar","nameLocation":"7046:6:0","scope":969,"usedErrors":[]},{"id":833,"nodeType":"ContractDefinition","src":"7135:281:0","nodes":[{"id":813,"nodeType":"VariableDeclaration","src":"7162:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7188:10:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":800,"name":"address","nodeType":"ElementaryTypeName","src":"7162:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":808,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7235:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":807,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7225:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":809,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7225:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":806,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7217:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":805,"name":"uint256","nodeType":"ElementaryTypeName","src":"7217:7:0","typeDescriptions":{}}},"id":810,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7217:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":804,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7209:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":803,"name":"uint160","nodeType":"ElementaryTypeName","src":"7209:7:0","typeDescriptions":{}}},"id":811,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7209:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":802,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":801,"name":"address","nodeType":"ElementaryTypeName","src":"7201:7:0","typeDescriptions":{}}},"id":812,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7201:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":819,"nodeType":"VariableDeclaration","src":"7262:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7283:2:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":815,"nodeType":"UserDefinedTypeName","pathNode":{"id":814,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7262:2:0"},"referencedDeclaration":83,"src":"7262:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":817,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":813,"src":"7291:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":816,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7288:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":818,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7288:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":832,"nodeType":"FunctionDefinition","src":"7309:105:0","nodes":[],"body":{"id":831,"nodeType":"Block","src":"7372:42:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":828,"name":"_addr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":821,"src":"7401:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":826,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":819,"src":"7389:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":827,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7389:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":829,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7389:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"functionReturnParameters":825,"id":830,"nodeType":"Return","src":"7382:25:0"}]},"functionSelector":"2d0335ab","implemented":true,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"7318:8:0","parameters":{"id":822,"nodeType":"ParameterList","parameters":[{"constant":false,"id":821,"mutability":"mutable","name":"_addr","nameLocation":"7335:5:0","nodeType":"VariableDeclaration","scope":832,"src":"7327:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":820,"name":"address","nodeType":"ElementaryTypeName","src":"7327:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7326:15:0"},"returnParameters":{"id":825,"nodeType":"ParameterList","parameters":[{"constant":false,"id":824,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":832,"src":"7363:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":823,"name":"uint256","nodeType":"ElementaryTypeName","src":"7363:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7362:9:0"},"scope":833,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"NonceGetter","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[833],"name":"NonceGetter","nameLocation":"7144:11:0","scope":969,"usedErrors":[]},{"id":852,"nodeType":"ContractDefinition","src":"7418:174:0","nodes":[{"id":835,"nodeType":"VariableDeclaration","src":"7448:18:0","nodes":[],"constant":false,"mutability":"mutable","name":"v","nameLocation":"7465:1:0","scope":852,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":834,"name":"uint256","nodeType":"ElementaryTypeName","src":"7448:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"id":843,"nodeType":"FunctionDefinition","src":"7473:36:0","nodes":[],"body":{"id":842,"nodeType":"Block","src":"7487:22:0","nodes":[],"statements":[{"expression":{"id":840,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":838,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7497:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"31","id":839,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7501:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"7497:5:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":841,"nodeType":"ExpressionStatement","src":"7497:5:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":836,"nodeType":"ParameterList","parameters":[],"src":"7484:2:0"},"returnParameters":{"id":837,"nodeType":"ParameterList","parameters":[],"src":"7487:0:0"},"scope":852,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":851,"nodeType":"FunctionDefinition","src":"7515:75:0","nodes":[],"body":{"id":850,"nodeType":"Block","src":"7565:25:0","nodes":[],"statements":[{"expression":{"id":848,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7582:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":847,"id":849,"nodeType":"Return","src":"7575:8:0"}]},"functionSelector":"20965255","implemented":true,"kind":"function","modifiers":[],"name":"getValue","nameLocation":"7524:8:0","parameters":{"id":844,"nodeType":"ParameterList","parameters":[],"src":"7532:2:0"},"returnParameters":{"id":847,"nodeType":"ParameterList","parameters":[{"constant":false,"id":846,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":851,"src":"7556:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":845,"name":"uint256","nodeType":"ElementaryTypeName","src":"7556:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7555:9:0"},"scope":852,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkedContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[852],"name":"ForkedContract","nameLocation":"7427:14:0","scope":969,"usedErrors":[]},{"id":968,"nodeType":"ContractDefinition","src":"7594:813:0","nodes":[{"id":866,"nodeType":"VariableDeclaration","src":"7620:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7646:10:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":853,"name":"address","nodeType":"ElementaryTypeName","src":"7620:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":861,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7693:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":860,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7683:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":862,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7683:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":859,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7675:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":858,"name":"uint256","nodeType":"ElementaryTypeName","src":"7675:7:0","typeDescriptions":{}}},"id":863,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7675:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":857,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7667:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":856,"name":"uint160","nodeType":"ElementaryTypeName","src":"7667:7:0","typeDescriptions":{}}},"id":864,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7667:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":855,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7659:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":854,"name":"address","nodeType":"ElementaryTypeName","src":"7659:7:0","typeDescriptions":{}}},"id":865,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7659:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":872,"nodeType":"VariableDeclaration","src":"7720:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7741:2:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":868,"nodeType":"UserDefinedTypeName","pathNode":{"id":867,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7720:2:0"},"referencedDeclaration":83,"src":"7720:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":870,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":866,"src":"7749:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":869,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7746:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":871,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7746:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":967,"nodeType":"FunctionDefinition","src":"7767:638:0","nodes":[],"body":{"id":966,"nodeType":"Block","src":"7791:614:0","nodes":[],"statements":[{"assignments":[876],"declarations":[{"constant":false,"id":876,"mutability":"mutable","name":"testAddr","nameLocation":"7809:8:0","nodeType":"VariableDeclaration","scope":966,"src":"7801:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":875,"name":"address","nodeType":"ElementaryTypeName","src":"7801:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":884,"initialValue":{"arguments":[{"arguments":[{"hexValue":"307831323334","id":881,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7836:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":880,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7828:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":879,"name":"uint160","nodeType":"ElementaryTypeName","src":"7828:7:0","typeDescriptions":{}}},"id":882,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7828:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":878,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7820:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":877,"name":"address","nodeType":"ElementaryTypeName","src":"7820:7:0","typeDescriptions":{}}},"id":883,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7820:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"7801:43:0"},{"assignments":[887],"declarations":[{"constant":false,"id":887,"mutability":"mutable","name":"fc","nameLocation":"7869:2:0","nodeType":"VariableDeclaration","scope":966,"src":"7854:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"},"typeName":{"id":886,"nodeType":"UserDefinedTypeName","pathNode":{"id":885,"name":"ForkedContract","nodeType":"IdentifierPath","referencedDeclaration":852,"src":"7854:14:0"},"referencedDeclaration":852,"src":"7854:14:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"visibility":"internal"}],"id":891,"initialValue":{"arguments":[{"id":889,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7889:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":888,"name":"ForkedContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":852,"src":"7874:14:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ForkedContract_$852_$","typeString":"type(contract ForkedContract)"}},"id":890,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7874:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"nodeType":"VariableDeclarationStatement","src":"7854:44:0"},{"expression":{"arguments":[{"hexValue":"666f726b31","id":895,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7929:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},"value":"fork1"},{"hexValue":"3132333435","id":896,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7938:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"}],"expression":{"id":892,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7909:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":894,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"7909:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":897,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7909:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":898,"nodeType":"ExpressionStatement","src":"7909:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":905,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":902,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7974:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":900,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7962:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":901,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7962:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":903,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7962:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3132333435","id":904,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7987:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"},"src":"7962:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":906,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7994:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":899,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"7954:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":907,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7954:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":908,"nodeType":"ExpressionStatement","src":"7954:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":914,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":910,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8036:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":911,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8036:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":912,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8036:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31","id":913,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8053:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"8036:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652031","id":915,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8056:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""},"value":"value should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""}],"id":909,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8028:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":916,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8028:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":917,"nodeType":"ExpressionStatement","src":"8028:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":925,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":919,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8094:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":920,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8094:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"31","id":923,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8122:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"}],"id":922,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8114:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":921,"name":"uint256","nodeType":"ElementaryTypeName","src":"8114:7:0","typeDescriptions":{}}},"id":924,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8114:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8094:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652031","id":926,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8126:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""},"value":"balance should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""}],"id":918,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8086:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":927,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8086:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":928,"nodeType":"ExpressionStatement","src":"8086:62:0"},{"expression":{"arguments":[{"hexValue":"666f726b32","id":932,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8179:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},"value":"fork2"},{"hexValue":"3233343536","id":933,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8188:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"}],"expression":{"id":929,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8159:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":931,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"8159:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":934,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8159:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":935,"nodeType":"ExpressionStatement","src":"8159:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":939,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8224:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":937,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8212:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":938,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"8212:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":940,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8212:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3233343536","id":941,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8237:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"},"src":"8212:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":943,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8244:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":936,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8204:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":944,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8204:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":945,"nodeType":"ExpressionStatement","src":"8204:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":951,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":947,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8286:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":948,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8286:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":949,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8286:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"32","id":950,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8303:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"src":"8286:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652032","id":952,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8306:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""},"value":"value should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""}],"id":946,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8278:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":953,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8278:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":954,"nodeType":"ExpressionStatement","src":"8278:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":962,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":956,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8344:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":957,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8344:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"32","id":960,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8372:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"}],"id":959,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8364:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":958,"name":"uint256","nodeType":"ElementaryTypeName","src":"8364:7:0","typeDescriptions":{}}},"id":961,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8364:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8344:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652032","id":963,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8376:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""},"value":"balance should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""}],"id":955,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8336:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":964,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8336:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":965,"nodeType":"ExpressionStatement","src":"8336:62:0"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"7776:3:0","parameters":{"id":873,"nodeType":"ParameterList","parameters":[],"src":"7779:2:0"},"returnParameters":{"id":874,"nodeType":"ParameterList","parameters":[],"src":"7791:0:0"},"scope":968,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkTester","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[968],"name":"ForkTester","nameLocation":"7603:10:0","scope":969,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ForkTester.json b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ForkTester.json new file mode 100644 index 0000000000000..94470339ecceb --- /dev/null +++ b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ForkTester.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"run","inputs":[],"outputs":[],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b5061070c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c040622614610030575b600080fd5b61003861003a565b005b604080517f71ee464d0000000000000000000000000000000000000000000000000000000081526004810191909152600560448201527f666f726b3100000000000000000000000000000000000000000000000000000060648201526130396024820152611234908190737109709ecfa91a80626ff3989d68f67f5b1dd12d906371ee464d906084016020604051808303816000875af11580156100e2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061010691906106b5565b506040517f2d0335ab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401602060405180830381865afa158015610185573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101a991906106ce565b67ffffffffffffffff1661303914610222576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f6e6f6e63652073686f756c64206265203132333435000000000000000000000060448201526064015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1663209652556040518163ffffffff1660e01b8152600401602060405180830381865afa15801561026d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061029191906106b5565b6001146102fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f76616c75652073686f756c6420626520310000000000000000000000000000006044820152606401610219565b60018273ffffffffffffffffffffffffffffffffffffffff16311461037b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f62616c616e63652073686f756c642062652031000000000000000000000000006044820152606401610219565b604080517f71ee464d0000000000000000000000000000000000000000000000000000000081526004810191909152600560448201527f666f726b320000000000000000000000000000000000000000000000000000006064820152615ba06024820152737109709ecfa91a80626ff3989d68f67f5b1dd12d906371ee464d906084016020604051808303816000875af115801561041d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044191906106b5565b506040517f2d0335ab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401602060405180830381865afa1580156104c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104e491906106ce565b67ffffffffffffffff16615ba014610558576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f6e6f6e63652073686f756c6420626520313233343500000000000000000000006044820152606401610219565b8073ffffffffffffffffffffffffffffffffffffffff1663209652556040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c791906106b5565b600214610630576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f76616c75652073686f756c6420626520320000000000000000000000000000006044820152606401610219565b60028273ffffffffffffffffffffffffffffffffffffffff1631146106b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f62616c616e63652073686f756c642062652032000000000000000000000000006044820152606401610219565b5050565b6000602082840312156106c757600080fd5b5051919050565b6000602082840312156106e057600080fd5b815167ffffffffffffffff811681146106f857600080fd5b939250505056fea164736f6c634300080f000a","sourceMap":"7594:813:0:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c040622614610030575b600080fd5b61003861003a565b005b604080517f71ee464d0000000000000000000000000000000000000000000000000000000081526004810191909152600560448201527f666f726b3100000000000000000000000000000000000000000000000000000060648201526130396024820152611234908190737109709ecfa91a80626ff3989d68f67f5b1dd12d906371ee464d906084016020604051808303816000875af11580156100e2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061010691906106b5565b506040517f2d0335ab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401602060405180830381865afa158015610185573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101a991906106ce565b67ffffffffffffffff1661303914610222576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f6e6f6e63652073686f756c64206265203132333435000000000000000000000060448201526064015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1663209652556040518163ffffffff1660e01b8152600401602060405180830381865afa15801561026d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061029191906106b5565b6001146102fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f76616c75652073686f756c6420626520310000000000000000000000000000006044820152606401610219565b60018273ffffffffffffffffffffffffffffffffffffffff16311461037b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f62616c616e63652073686f756c642062652031000000000000000000000000006044820152606401610219565b604080517f71ee464d0000000000000000000000000000000000000000000000000000000081526004810191909152600560448201527f666f726b320000000000000000000000000000000000000000000000000000006064820152615ba06024820152737109709ecfa91a80626ff3989d68f67f5b1dd12d906371ee464d906084016020604051808303816000875af115801561041d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044191906106b5565b506040517f2d0335ab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401602060405180830381865afa1580156104c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104e491906106ce565b67ffffffffffffffff16615ba014610558576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f6e6f6e63652073686f756c6420626520313233343500000000000000000000006044820152606401610219565b8073ffffffffffffffffffffffffffffffffffffffff1663209652556040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c791906106b5565b600214610630576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f76616c75652073686f756c6420626520320000000000000000000000000000006044820152606401610219565b60028273ffffffffffffffffffffffffffffffffffffffff1631146106b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f62616c616e63652073686f756c642062652032000000000000000000000000006044820152606401610219565b5050565b6000602082840312156106c757600080fd5b5051919050565b6000602082840312156106e057600080fd5b815167ffffffffffffffff811681146106f857600080fd5b939250505056fea164736f6c634300080f000a","sourceMap":"7594:813:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7767:638;;;:::i;:::-;;;7909:35;;;;;;;;;238:21:1;;;;295:1;275:18;;;268:29;333:7;313:18;;;306:35;7938:5:0;393:20:1;;;386:36;7836:6:0;;;;7909:19;;;;358::1;;7909:35:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;7962:21:0;;;;;798:42:1;786:55;;7962:21:0;;;768:74:1;7962:11:0;;;;741:18:1;;7962:21:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:30;;7987:5;7962:30;7954:64;;;;;;;1348:2:1;7954:64:0;;;1330:21:1;1387:2;1367:18;;;1360:30;1426:23;1406:18;;;1399:51;1467:18;;7954:64:0;;;;;;;;;8036:2;:11;;;:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;8053:1;8036:18;8028:48;;;;;;;1698:2:1;8028:48:0;;;1680:21:1;1737:2;1717:18;;;1710:30;1776:19;1756:18;;;1749:47;1813:18;;8028:48:0;1496:341:1;8028:48:0;8122:1;8094:8;:16;;;:30;8086:62;;;;;;;2044:2:1;8086:62:0;;;2026:21:1;2083:2;2063:18;;;2056:30;2122:21;2102:18;;;2095:49;2161:18;;8086:62:0;1842:343:1;8086:62:0;8159:35;;;;;;;;;2414:21:1;;;;2471:1;2451:18;;;2444:29;2509:7;2489:18;;;2482:35;8188:5:0;2569:20:1;;;2562:36;8159:19:0;;;;2534::1;;8159:35:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;8212:21:0;;;;;798:42:1;786:55;;8212:21:0;;;768:74:1;8212:11:0;;;;741:18:1;;8212:21:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:30;;8237:5;8212:30;8204:64;;;;;;;1348:2:1;8204:64:0;;;1330:21:1;1387:2;1367:18;;;1360:30;1426:23;1406:18;;;1399:51;1467:18;;8204:64:0;1146:345:1;8204:64:0;8286:2;:11;;;:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;8303:1;8286:18;8278:48;;;;;;;2811:2:1;8278:48:0;;;2793:21:1;2850:2;2830:18;;;2823:30;2889:19;2869:18;;;2862:47;2926:18;;8278:48:0;2609:341:1;8278:48:0;8372:1;8344:8;:16;;;:30;8336:62;;;;;;;3157:2:1;8336:62:0;;;3139:21:1;3196:2;3176:18;;;3169:30;3235:21;3215:18;;;3208:49;3274:18;;8336:62:0;2955:343:1;8336:62:0;7791:614;;7767:638::o;433:184:1:-;503:6;556:2;544:9;535:7;531:23;527:32;524:52;;;572:1;569;562:12;524:52;-1:-1:-1;595:16:1;;433:184;-1:-1:-1;433:184:1:o;853:288::-;922:6;975:2;963:9;954:7;950:23;946:32;943:52;;;991:1;988;981:12;943:52;1023:9;1017:16;1073:18;1066:5;1062:30;1055:5;1052:41;1042:69;;1107:1;1104;1097:12;1042:69;1130:5;853:288;-1:-1:-1;;;853:288:1:o","linkReferences":{}},"methodIdentifiers":{"run()":"c0406226"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"run\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"ForkTester\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28\",\"dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"run"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"ForkTester"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da","urls":["bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28","dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":969,"exportedSymbols":{"FooBar":[799],"ForkTester":[968],"ForkedContract":[852],"NonceGetter":[833],"ScriptExample":[786],"Vm":[83],"console":[220]},"nodeType":"SourceUnit","src":"32:8375:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":83,"nodeType":"ContractDefinition","src":"120:969:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":83,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":61,"nodeType":"FunctionDefinition","src":"739:108:0","nodes":[],"functionSelector":"3ebf73b4","implemented":false,"kind":"function","modifiers":[],"name":"getDeployedCode","nameLocation":"748:15:0","parameters":{"id":57,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56,"mutability":"mutable","name":"artifactPath","nameLocation":"780:12:0","nodeType":"VariableDeclaration","scope":61,"src":"764:28:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":55,"name":"string","nodeType":"ElementaryTypeName","src":"764:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"763:30:0"},"returnParameters":{"id":60,"nodeType":"ParameterList","parameters":[{"constant":false,"id":59,"mutability":"mutable","name":"runtimeBytecode","nameLocation":"830:15:0","nodeType":"VariableDeclaration","scope":61,"src":"817:28:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":58,"name":"bytes","nodeType":"ElementaryTypeName","src":"817:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"816:30:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":68,"nodeType":"FunctionDefinition","src":"852:74:0","nodes":[],"functionSelector":"b4d6c782","implemented":false,"kind":"function","modifiers":[],"name":"etch","nameLocation":"861:4:0","parameters":{"id":66,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"target","nameLocation":"874:6:0","nodeType":"VariableDeclaration","scope":68,"src":"866:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":62,"name":"address","nodeType":"ElementaryTypeName","src":"866:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":65,"mutability":"mutable","name":"newRuntimeBytecode","nameLocation":"897:18:0","nodeType":"VariableDeclaration","scope":68,"src":"882:33:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":64,"name":"bytes","nodeType":"ElementaryTypeName","src":"882:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"865:51:0"},"returnParameters":{"id":67,"nodeType":"ParameterList","parameters":[],"src":"925:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":73,"nodeType":"FunctionDefinition","src":"931:51:0","nodes":[],"functionSelector":"ea060291","implemented":false,"kind":"function","modifiers":[],"name":"allowCheatcodes","nameLocation":"940:15:0","parameters":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"account","nameLocation":"964:7:0","nodeType":"VariableDeclaration","scope":73,"src":"956:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":69,"name":"address","nodeType":"ElementaryTypeName","src":"956:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"955:17:0"},"returnParameters":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"981:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":82,"nodeType":"FunctionDefinition","src":"987:100:0","nodes":[],"functionSelector":"71ee464d","implemented":false,"kind":"function","modifiers":[],"name":"createSelectFork","nameLocation":"996:16:0","parameters":{"id":78,"nodeType":"ParameterList","parameters":[{"constant":false,"id":75,"mutability":"mutable","name":"forkName","nameLocation":"1029:8:0","nodeType":"VariableDeclaration","scope":82,"src":"1013:24:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":74,"name":"string","nodeType":"ElementaryTypeName","src":"1013:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":77,"mutability":"mutable","name":"blockNumber","nameLocation":"1047:11:0","nodeType":"VariableDeclaration","scope":82,"src":"1039:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":76,"name":"uint256","nodeType":"ElementaryTypeName","src":"1039:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1012:47:0"},"returnParameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":82,"src":"1078:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":79,"name":"uint256","nodeType":"ElementaryTypeName","src":"1078:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1077:9:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[83],"name":"Vm","nameLocation":"130:2:0","scope":969,"usedErrors":[]},{"id":220,"nodeType":"ContractDefinition","src":"1144:1851:0","nodes":[{"id":89,"nodeType":"VariableDeclaration","src":"1166:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"1183:15:0","scope":220,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":84,"name":"address","nodeType":"ElementaryTypeName","src":"1166:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":87,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1209:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":86,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":85,"name":"address","nodeType":"ElementaryTypeName","src":"1201:7:0","typeDescriptions":{}}},"id":88,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1201:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1259:235:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1432:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1451:37:0","statements":[{"nodeType":"YulAssignment","src":"1465:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1474:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1465:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":95,"isOffset":false,"isSlot":false,"src":"1474:4:0","valueSize":1},{"declaration":102,"isOffset":false,"isSlot":false,"src":"1465:5:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1442:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"1268:25:0","parameters":{"id":96,"nodeType":"ParameterList","parameters":[{"constant":false,"id":95,"mutability":"mutable","name":"fnIn","nameLocation":"1331:4:0","nodeType":"VariableDeclaration","scope":106,"src":"1294:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":94,"nodeType":"FunctionTypeName","parameterTypes":{"id":92,"nodeType":"ParameterList","parameters":[{"constant":false,"id":91,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":94,"src":"1303:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":90,"name":"bytes","nodeType":"ElementaryTypeName","src":"1303:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1302:14:0"},"returnParameterTypes":{"id":93,"nodeType":"ParameterList","parameters":[],"src":"1331:0:0"},"src":"1294:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"1293:43:0"},"returnParameters":{"id":103,"nodeType":"ParameterList","parameters":[{"constant":false,"id":102,"mutability":"mutable","name":"fnOut","nameLocation":"1421:5:0","nodeType":"VariableDeclaration","scope":106,"src":"1384:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":101,"nodeType":"FunctionTypeName","parameterTypes":{"id":99,"nodeType":"ParameterList","parameters":[{"constant":false,"id":98,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":101,"src":"1393:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":97,"name":"bytes","nodeType":"ElementaryTypeName","src":"1393:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1392:14:0"},"returnParameterTypes":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"1421:0:0"},"src":"1384:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1383:44:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":118,"nodeType":"FunctionDefinition","src":"1500:133:0","nodes":[],"body":{"id":117,"nodeType":"Block","src":"1561:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":114,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1618:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":112,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":134,"src":"1597:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":111,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1571:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":113,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":115,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":116,"nodeType":"ExpressionStatement","src":"1571:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1509:15:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"payload","nameLocation":"1538:7:0","nodeType":"VariableDeclaration","scope":118,"src":"1525:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":107,"name":"bytes","nodeType":"ElementaryTypeName","src":"1525:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1524:22:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1561:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":134,"nodeType":"FunctionDefinition","src":"1639:380:0","nodes":[],"body":{"id":133,"nodeType":"Block","src":"1703:316:0","nodes":[],"statements":[{"assignments":[124],"declarations":[{"constant":false,"id":124,"mutability":"mutable","name":"payloadLength","nameLocation":"1721:13:0","nodeType":"VariableDeclaration","scope":133,"src":"1713:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":123,"name":"uint256","nodeType":"ElementaryTypeName","src":"1713:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":127,"initialValue":{"expression":{"id":125,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":120,"src":"1737:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":126,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1737:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1713:38:0"},{"assignments":[129],"declarations":[{"constant":false,"id":129,"mutability":"mutable","name":"consoleAddress","nameLocation":"1769:14:0","nodeType":"VariableDeclaration","scope":133,"src":"1761:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":128,"name":"address","nodeType":"ElementaryTypeName","src":"1761:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":131,"initialValue":{"id":130,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":89,"src":"1786:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1761:40:0"},{"AST":{"nodeType":"YulBlock","src":"1863:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1877:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1901:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1910:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1897:3:0"},"nodeType":"YulFunctionCall","src":"1897:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1881:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1926:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1946:3:0"},"nodeType":"YulFunctionCall","src":"1946:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1953:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1969:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1983:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1998:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"2001:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1935:10:0"},"nodeType":"YulFunctionCall","src":"1935:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1930:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":129,"isOffset":false,"isSlot":false,"src":"1953:14:0","valueSize":1},{"declaration":120,"isOffset":false,"isSlot":false,"src":"1901:7:0","valueSize":1},{"declaration":124,"isOffset":false,"isSlot":false,"src":"1983:13:0","valueSize":1}],"id":132,"nodeType":"InlineAssembly","src":"1854:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1648:19:0","parameters":{"id":121,"nodeType":"ParameterList","parameters":[{"constant":false,"id":120,"mutability":"mutable","name":"payload","nameLocation":"1681:7:0","nodeType":"VariableDeclaration","scope":134,"src":"1668:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":119,"name":"bytes","nodeType":"ElementaryTypeName","src":"1668:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1667:22:0"},"returnParameters":{"id":122,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":220,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":148,"nodeType":"FunctionDefinition","src":"2025:164:0","nodes":[],"body":{"id":147,"nodeType":"Block","src":"2070:119:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":142,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2120:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":143,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":136,"src":"2135:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":140,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2096:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":141,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2096: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":144,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2096:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":139,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2080:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":145,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2080:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":146,"nodeType":"ExpressionStatement","src":"2080:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2034:3:0","parameters":{"id":137,"nodeType":"ParameterList","parameters":[{"constant":false,"id":136,"mutability":"mutable","name":"p0","nameLocation":"2052:2:0","nodeType":"VariableDeclaration","scope":148,"src":"2038:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":135,"name":"string","nodeType":"ElementaryTypeName","src":"2038:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2037:18:0"},"returnParameters":{"id":138,"nodeType":"ParameterList","parameters":[],"src":"2070:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":165,"nodeType":"FunctionDefinition","src":"2195:182:0","nodes":[],"body":{"id":164,"nodeType":"Block","src":"2249:128:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":158,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2299:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":159,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":150,"src":"2319:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":160,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":152,"src":"2323:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":156,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2275:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":157,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2275: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":161,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2275:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":155,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2259:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":162,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2259:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":163,"nodeType":"ExpressionStatement","src":"2259:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2204:3:0","parameters":{"id":153,"nodeType":"ParameterList","parameters":[{"constant":false,"id":150,"mutability":"mutable","name":"p0","nameLocation":"2222:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2208:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":149,"name":"string","nodeType":"ElementaryTypeName","src":"2208:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":152,"mutability":"mutable","name":"p1","nameLocation":"2231:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2226:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":151,"name":"bool","nodeType":"ElementaryTypeName","src":"2226:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"2207:27:0"},"returnParameters":{"id":154,"nodeType":"ParameterList","parameters":[],"src":"2249:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":182,"nodeType":"FunctionDefinition","src":"2383:188:0","nodes":[],"body":{"id":181,"nodeType":"Block","src":"2440:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":175,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2490:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":176,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":167,"src":"2513:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":177,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":169,"src":"2517:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":173,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2466:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":174,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2466: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":178,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2466:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":172,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2450:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":179,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2450:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":180,"nodeType":"ExpressionStatement","src":"2450:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2392:3:0","parameters":{"id":170,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"mutability":"mutable","name":"p0","nameLocation":"2410:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2396:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":166,"name":"string","nodeType":"ElementaryTypeName","src":"2396:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":169,"mutability":"mutable","name":"p1","nameLocation":"2422:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2414:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":168,"name":"uint256","nodeType":"ElementaryTypeName","src":"2414:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2395:30:0"},"returnParameters":{"id":171,"nodeType":"ParameterList","parameters":[],"src":"2440:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":199,"nodeType":"FunctionDefinition","src":"2577:188:0","nodes":[],"body":{"id":198,"nodeType":"Block","src":"2634:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":192,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2684:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":193,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":184,"src":"2707:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":194,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"2711:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":190,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2660:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":191,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2660: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":195,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2660:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":189,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2644:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":196,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2644:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":197,"nodeType":"ExpressionStatement","src":"2644:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2586:3:0","parameters":{"id":187,"nodeType":"ParameterList","parameters":[{"constant":false,"id":184,"mutability":"mutable","name":"p0","nameLocation":"2604:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2590:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":183,"name":"string","nodeType":"ElementaryTypeName","src":"2590:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":186,"mutability":"mutable","name":"p1","nameLocation":"2616:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2608:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":185,"name":"address","nodeType":"ElementaryTypeName","src":"2608:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2589:30:0"},"returnParameters":{"id":188,"nodeType":"ParameterList","parameters":[],"src":"2634:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":219,"nodeType":"FunctionDefinition","src":"2771:222:0","nodes":[],"body":{"id":218,"nodeType":"Block","src":"2852:141:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":211,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2902:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":212,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":201,"src":"2931:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":213,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":203,"src":"2935:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":214,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":205,"src":"2939:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":209,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2878:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":210,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2878: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":215,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2878:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":208,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2862:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":216,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2862:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":217,"nodeType":"ExpressionStatement","src":"2862:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2780:3:0","parameters":{"id":206,"nodeType":"ParameterList","parameters":[{"constant":false,"id":201,"mutability":"mutable","name":"p0","nameLocation":"2798:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2784:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":200,"name":"string","nodeType":"ElementaryTypeName","src":"2784:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":203,"mutability":"mutable","name":"p1","nameLocation":"2816:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2802:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":202,"name":"string","nodeType":"ElementaryTypeName","src":"2802:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":205,"mutability":"mutable","name":"p2","nameLocation":"2834:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2820:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":204,"name":"string","nodeType":"ElementaryTypeName","src":"2820:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2783:54:0"},"returnParameters":{"id":207,"nodeType":"ParameterList","parameters":[],"src":"2852:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[220],"name":"console","nameLocation":"1152:7:0","scope":969,"usedErrors":[]},{"id":786,"nodeType":"ContractDefinition","src":"3123:3912:0","nodes":[{"id":235,"nodeType":"VariableDeclaration","src":"3152:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"3178:10:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":222,"name":"address","nodeType":"ElementaryTypeName","src":"3152:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3225:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":229,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"3215:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":231,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3215:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":228,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3207:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":227,"name":"uint256","nodeType":"ElementaryTypeName","src":"3207:7:0","typeDescriptions":{}}},"id":232,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3207:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":226,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3199:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":225,"name":"uint160","nodeType":"ElementaryTypeName","src":"3199:7:0","typeDescriptions":{}}},"id":233,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3199:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":224,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3191:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":223,"name":"address","nodeType":"ElementaryTypeName","src":"3191:7:0","typeDescriptions":{}}},"id":234,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3191:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":241,"nodeType":"VariableDeclaration","src":"3252:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"3273:2:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":237,"nodeType":"UserDefinedTypeName","pathNode":{"id":236,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"3252:2:0"},"referencedDeclaration":83,"src":"3252:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":239,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":235,"src":"3281:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":238,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"3278:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":240,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3278:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":243,"nodeType":"VariableDeclaration","src":"3356:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"3371:7:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":242,"name":"uint256","nodeType":"ElementaryTypeName","src":"3356:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":456,"nodeType":"FunctionDefinition","src":"3468:1428:0","nodes":[],"body":{"id":455,"nodeType":"Block","src":"3490:1406:0","nodes":[],"statements":[{"assignments":[248],"declarations":[{"constant":false,"id":248,"mutability":"mutable","name":"x","nameLocation":"3505:1:0","nodeType":"VariableDeclaration","scope":455,"src":"3500:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":247,"name":"bool","nodeType":"ElementaryTypeName","src":"3500:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":254,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":251,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3518:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":252,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"3534:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":249,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3509:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":250,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"3509:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"3500:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":258,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3562:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":259,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":248,"src":"3585:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":255,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3550:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":257,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":165,"src":"3550:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":260,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3550:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":261,"nodeType":"ExpressionStatement","src":"3550:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":265,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3610:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":268,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3635:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":267,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3627:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":266,"name":"address","nodeType":"ElementaryTypeName","src":"3627:7:0","typeDescriptions":{}}},"id":269,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3627:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":262,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3598:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3598:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3598:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":271,"nodeType":"ExpressionStatement","src":"3598:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":275,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3663:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":280,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3701:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":279,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3693:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":278,"name":"address","nodeType":"ElementaryTypeName","src":"3693:7:0","typeDescriptions":{}}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3693:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":276,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3681:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3681:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":282,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":272,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3651:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":274,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3651:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":283,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3651:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":284,"nodeType":"ExpressionStatement","src":"3651:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":288,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3730:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":291,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3753:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3753:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":290,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3745:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":289,"name":"address","nodeType":"ElementaryTypeName","src":"3745:7:0","typeDescriptions":{}}},"id":293,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3745:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":285,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3718:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3718:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3718:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":295,"nodeType":"ExpressionStatement","src":"3718:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":299,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3787:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":304,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3823:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":305,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3823:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":303,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3815:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":302,"name":"address","nodeType":"ElementaryTypeName","src":"3815:7:0","typeDescriptions":{}}},"id":306,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3815:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":300,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3803:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3803:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3803:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":296,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3775:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":298,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3775:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":308,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3775:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":309,"nodeType":"ExpressionStatement","src":"3775:61:0"},{"assignments":[311],"declarations":[{"constant":false,"id":311,"mutability":"mutable","name":"json","nameLocation":"3861:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3847:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":310,"name":"string","nodeType":"ElementaryTypeName","src":"3847:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":313,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3868:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3847:55:0"},{"assignments":[318],"declarations":[{"constant":false,"id":318,"mutability":"mutable","name":"keys","nameLocation":"3928:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3912:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":316,"name":"string","nodeType":"ElementaryTypeName","src":"3912:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":317,"nodeType":"ArrayTypeName","src":"3912:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":324,"initialValue":{"arguments":[{"id":321,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":311,"src":"3952:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3958:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":319,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3935:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":320,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3935:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":323,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3935:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3912:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":328,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3995:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":329,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4003:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":331,"indexExpression":{"hexValue":"30","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4008:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4003:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":332,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4012:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":334,"indexExpression":{"hexValue":"31","id":333,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4017:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4012:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":325,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3983:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":327,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":219,"src":"3983:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3983:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":336,"nodeType":"ExpressionStatement","src":"3983:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":340,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4042:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":337,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4031:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":339,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4031:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4031:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":342,"nodeType":"ExpressionStatement","src":"4031:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":350,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4098:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4090:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":348,"name":"uint160","nodeType":"ElementaryTypeName","src":"4090:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4090:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":347,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4082:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":346,"name":"address","nodeType":"ElementaryTypeName","src":"4082:7:0","typeDescriptions":{}}},"id":352,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4082:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":343,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4068:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":345,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"4068:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":353,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4068:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":354,"nodeType":"ExpressionStatement","src":"4068:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":358,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4126:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":355,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4115:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":357,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4115:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":359,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4115:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":360,"nodeType":"ExpressionStatement","src":"4115:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":364,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4163:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":367,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4198:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":368,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"4198:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":366,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4190:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":365,"name":"address","nodeType":"ElementaryTypeName","src":"4190:7:0","typeDescriptions":{}}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4190:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":361,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4151:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4151:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":370,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4151:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":371,"nodeType":"ExpressionStatement","src":"4151:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":375,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4232:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":378,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4270:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":377,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4262:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":376,"name":"address","nodeType":"ElementaryTypeName","src":"4262:7:0","typeDescriptions":{}}},"id":379,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4262:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":372,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4220:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":374,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4220:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":380,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4220:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":381,"nodeType":"ExpressionStatement","src":"4220:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4297:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":382,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4286:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4286:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":386,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4286:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":387,"nodeType":"ExpressionStatement","src":"4286:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4322:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":390,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"4322:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":391,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4322:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":392,"nodeType":"ExpressionStatement","src":"4322:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":396,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4357:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":393,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4346:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4346:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":397,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4346:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":398,"nodeType":"ExpressionStatement","src":"4346:33:0"},{"assignments":[400],"declarations":[{"constant":false,"id":400,"mutability":"mutable","name":"tmpNonceGetter","nameLocation":"4480:14:0","nodeType":"VariableDeclaration","scope":455,"src":"4472:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":399,"name":"address","nodeType":"ElementaryTypeName","src":"4472:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":413,"initialValue":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"74656d70206e6f6e6365207465737420676574746572","id":408,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4531:24:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""},"value":"temp nonce test getter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""}],"id":407,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"4521:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":409,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4521:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":406,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4513:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":405,"name":"uint256","nodeType":"ElementaryTypeName","src":"4513:7:0","typeDescriptions":{}}},"id":410,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4513:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":404,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4505:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":403,"name":"uint160","nodeType":"ElementaryTypeName","src":"4505:7:0","typeDescriptions":{}}},"id":411,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4505:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":402,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4497:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":401,"name":"address","nodeType":"ElementaryTypeName","src":"4497:7:0","typeDescriptions":{}}},"id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4497:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"4472:87:0"},{"expression":{"arguments":[{"id":417,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4577:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"hexValue":"5363726970744578616d706c652e732e736f6c3a4e6f6e6365476574746572","id":420,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4612:33:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""},"value":"ScriptExample.s.sol:NonceGetter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""}],"expression":{"id":418,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4593:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getDeployedCode","nodeType":"MemberAccess","referencedDeclaration":61,"src":"4593:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) view external returns (bytes memory)"}},"id":421,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4593:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":414,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4569:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":416,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"etch","nodeType":"MemberAccess","referencedDeclaration":68,"src":"4569:7:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory) external"}},"id":422,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4569:78:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":423,"nodeType":"ExpressionStatement","src":"4569:78:0"},{"expression":{"arguments":[{"id":427,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4676:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":424,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4657:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":426,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"allowCheatcodes","nodeType":"MemberAccess","referencedDeclaration":73,"src":"4657:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":428,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":429,"nodeType":"ExpressionStatement","src":"4657:34:0"},{"assignments":[431],"declarations":[{"constant":false,"id":431,"mutability":"mutable","name":"v","nameLocation":"4709:1:0","nodeType":"VariableDeclaration","scope":455,"src":"4701:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":430,"name":"uint256","nodeType":"ElementaryTypeName","src":"4701:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":441,"initialValue":{"arguments":[{"arguments":[{"id":438,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4758:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":437,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4750:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":436,"name":"address","nodeType":"ElementaryTypeName","src":"4750:7:0","typeDescriptions":{}}},"id":439,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4750:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"arguments":[{"id":433,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4725:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":432,"name":"NonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":833,"src":"4713:11:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_NonceGetter_$833_$","typeString":"type(contract NonceGetter)"}},"id":434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_NonceGetter_$833","typeString":"contract NonceGetter"}},"id":435,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":832,"src":"4713:36:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view external returns (uint256)"}},"id":440,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"4701:63:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683a","id":445,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4786:68:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},"value":"nonce from nonce getter, no explicit access required with vm.etch:"},{"id":446,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":431,"src":"4856:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":442,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4774:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":444,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"4774:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4774:84:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":448,"nodeType":"ExpressionStatement","src":"4774:84:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":452,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4881:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":449,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4869:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":451,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"4869:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4869:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":454,"nodeType":"ExpressionStatement","src":"4869:20:0"}]},"documentation":{"id":244,"nodeType":"StructuredDocumentation","src":"3385:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"3477:3:0","parameters":{"id":245,"nodeType":"ParameterList","parameters":[],"src":"3480:2:0"},"returnParameters":{"id":246,"nodeType":"ParameterList","parameters":[],"src":"3490:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":689,"nodeType":"FunctionDefinition","src":"4963:1333:0","nodes":[],"body":{"id":688,"nodeType":"Block","src":"4994:1302:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":463,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5016:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":470,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5059:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":469,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5051:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":468,"name":"address","nodeType":"ElementaryTypeName","src":"5051:7:0","typeDescriptions":{}}},"id":471,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5051:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":466,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5039:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":467,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5039:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5039:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":465,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5031:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":464,"name":"uint256","nodeType":"ElementaryTypeName","src":"5031:7:0","typeDescriptions":{}}},"id":473,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5031:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":460,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5004:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":462,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"5004:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":474,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5004:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":475,"nodeType":"ExpressionStatement","src":"5004:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":479,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5090:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":476,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5078:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":478,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5078:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":480,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5078:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":481,"nodeType":"ExpressionStatement","src":"5078:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":482,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5117:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5117:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":485,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5117:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":486,"nodeType":"ExpressionStatement","src":"5117:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":490,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5152:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":487,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5141:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":489,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5141:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":491,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5141:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":492,"nodeType":"ExpressionStatement","src":"5141:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":496,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5188:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":493,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5177:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5177:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":497,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5177:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":498,"nodeType":"ExpressionStatement","src":"5177:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":502,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5226:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":499,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5214:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5214:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":503,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5214:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":504,"nodeType":"ExpressionStatement","src":"5214:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5291:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":511,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5283:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":510,"name":"uint160","nodeType":"ElementaryTypeName","src":"5283:7:0","typeDescriptions":{}}},"id":513,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5283:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5275:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":508,"name":"address","nodeType":"ElementaryTypeName","src":"5275:7:0","typeDescriptions":{}}},"id":514,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5275:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":505,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5257:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":507,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5257:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":515,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5257:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":516,"nodeType":"ExpressionStatement","src":"5257:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":520,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5323:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":517,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5312:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":519,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5312:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":521,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5312:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":522,"nodeType":"ExpressionStatement","src":"5312:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":526,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5362:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":523,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5351:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":525,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5351:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5351:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":528,"nodeType":"ExpressionStatement","src":"5351:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":532,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5404:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":529,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5390:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":531,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":785,"src":"5390:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":533,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5390:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":534,"nodeType":"ExpressionStatement","src":"5390:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":535,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5431:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":537,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5431:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":538,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5431:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":539,"nodeType":"ExpressionStatement","src":"5431:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":543,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5470:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":540,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5459:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":542,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5459:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5459:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":545,"nodeType":"ExpressionStatement","src":"5459:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":549,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5511:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":546,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5499:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5499:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":550,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5499:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":551,"nodeType":"ExpressionStatement","src":"5499:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":559,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5572:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":558,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5564:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":557,"name":"uint160","nodeType":"ElementaryTypeName","src":"5564:7:0","typeDescriptions":{}}},"id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5564:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":556,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5556:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":555,"name":"address","nodeType":"ElementaryTypeName","src":"5556:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5556:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":552,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5538:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":554,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5538:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5538:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":563,"nodeType":"ExpressionStatement","src":"5538:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":567,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5604:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":564,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5591:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":758,"src":"5591:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":568,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5591:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":569,"nodeType":"ExpressionStatement","src":"5591:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":570,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5623:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5623:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5623:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"5623:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5664:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5652:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5652:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5652:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"5652:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":588,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5725:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":587,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5717:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":586,"name":"uint160","nodeType":"ElementaryTypeName","src":"5717:7:0","typeDescriptions":{}}},"id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5717:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":585,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5709:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":584,"name":"address","nodeType":"ElementaryTypeName","src":"5709:7:0","typeDescriptions":{}}},"id":590,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5709:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5696:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5696:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":591,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5696:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":592,"nodeType":"ExpressionStatement","src":"5696:40:0"},{"assignments":[595],"declarations":[{"constant":false,"id":595,"mutability":"mutable","name":"x","nameLocation":"5753:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5746:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":594,"nodeType":"UserDefinedTypeName","pathNode":{"id":593,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5746:6:0"},"referencedDeclaration":799,"src":"5746:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":601,"initialValue":{"arguments":[{"hexValue":"31323334","id":599,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5768:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":598,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5757:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":597,"nodeType":"UserDefinedTypeName","pathNode":{"id":596,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5761:6:0"},"referencedDeclaration":799,"src":"5761:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":600,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5757:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5746:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":607,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":603,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":595,"src":"5791:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"5791:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5791:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":606,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5802:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"5791:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323334","id":608,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5808:35:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""},"value":"FooBar: foo in create is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""}],"id":602,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"5783:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":609,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5783:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":610,"nodeType":"ExpressionStatement","src":"5783:61:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":614,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5867:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":611,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5855:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":613,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5855:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":615,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5855:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":616,"nodeType":"ExpressionStatement","src":"5855:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5917:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":623,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5909:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":622,"name":"uint160","nodeType":"ElementaryTypeName","src":"5909:7:0","typeDescriptions":{}}},"id":625,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5909:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":621,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5901:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":620,"name":"address","nodeType":"ElementaryTypeName","src":"5901:7:0","typeDescriptions":{}}},"id":626,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5901:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":617,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5888:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":627,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5888:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":628,"nodeType":"ExpressionStatement","src":"5888:38:0"},{"assignments":[631],"declarations":[{"constant":false,"id":631,"mutability":"mutable","name":"y","nameLocation":"5943:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5936:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":630,"nodeType":"UserDefinedTypeName","pathNode":{"id":629,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5936:6:0"},"referencedDeclaration":799,"src":"5936:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":645,"initialValue":{"arguments":[{"hexValue":"31323334","id":643,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5986:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":634,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5947:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":633,"nodeType":"UserDefinedTypeName","pathNode":{"id":632,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5951:6:0"},"referencedDeclaration":799,"src":"5951:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":642,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":639,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5980:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":638,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5972:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":637,"name":"uint256","nodeType":"ElementaryTypeName","src":"5972:7:0","typeDescriptions":{}}},"id":640,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5972:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":636,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5964:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":635,"name":"bytes32","nodeType":"ElementaryTypeName","src":"5964:7:0","typeDescriptions":{}}},"id":641,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5964:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"5947:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":644,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5947:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5936:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":651,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":647,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":631,"src":"6009:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":648,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"6009:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":649,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6009:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":650,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6020:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"6009:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e2063726561746532206973206e6f742031323334","id":652,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6026:36:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""},"value":"FooBar: foo in create2 is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""}],"id":646,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"6001:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6001:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":654,"nodeType":"ExpressionStatement","src":"6001:62:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":658,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6085:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":655,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6073:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":657,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6073:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":659,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6073:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":660,"nodeType":"ExpressionStatement","src":"6073:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":661,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6177:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"6177:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":664,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6177:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":665,"nodeType":"ExpressionStatement","src":"6177:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":669,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6212:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":668,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"6201:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":667,"nodeType":"UserDefinedTypeName","pathNode":{"id":666,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"6205:6:0"},"referencedDeclaration":799,"src":"6205:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":670,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6201:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":671,"nodeType":"ExpressionStatement","src":"6201:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":675,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6240:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":682,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6281:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":681,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6273:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":680,"name":"address","nodeType":"ElementaryTypeName","src":"6273:7:0","typeDescriptions":{}}},"id":683,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6273:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":678,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6261:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"6261:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":684,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6261:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":677,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6253:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":676,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:0","typeDescriptions":{}}},"id":685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6253:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":672,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6228:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":674,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"6228:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":686,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6228:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":687,"nodeType":"ExpressionStatement","src":"6228:61:0"}]},"documentation":{"id":457,"nodeType":"StructuredDocumentation","src":"4902:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"4972:12:0","parameters":{"id":458,"nodeType":"ParameterList","parameters":[],"src":"4984:2:0"},"returnParameters":{"id":459,"nodeType":"ParameterList","parameters":[],"src":"4994:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":713,"nodeType":"FunctionDefinition","src":"6391:143:0","nodes":[],"body":{"id":712,"nodeType":"Block","src":"6440:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":698,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":692,"src":"6462:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":695,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6450:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":697,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6450:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":699,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6450:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":700,"nodeType":"ExpressionStatement","src":"6450:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6487:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":707,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"6515:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":708,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"6515:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":706,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6507:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":705,"name":"address","nodeType":"ElementaryTypeName","src":"6507:7:0","typeDescriptions":{}}},"id":709,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6507:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":701,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6475:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"6475:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":710,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6475:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":711,"nodeType":"ExpressionStatement","src":"6475:52:0"}]},"documentation":{"id":690,"nodeType":"StructuredDocumentation","src":"6302:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"6400:5:0","parameters":{"id":693,"nodeType":"ParameterList","parameters":[{"constant":false,"id":692,"mutability":"mutable","name":"_v","nameLocation":"6422:2:0","nodeType":"VariableDeclaration","scope":713,"src":"6406:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":691,"name":"string","nodeType":"ElementaryTypeName","src":"6406:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6405:20:0"},"returnParameters":{"id":694,"nodeType":"ParameterList","parameters":[],"src":"6440:0:0"},"scope":786,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":728,"nodeType":"FunctionDefinition","src":"6540:95:0","nodes":[],"body":{"id":727,"nodeType":"Block","src":"6584:51:0","nodes":[],"statements":[{"expression":{"id":719,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6594:9:0","subExpression":{"id":718,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6594:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":720,"nodeType":"ExpressionStatement","src":"6594:9:0"},{"expression":{"arguments":[{"id":724,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":715,"src":"6625:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":721,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6613:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":723,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6613:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":725,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6613:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":726,"nodeType":"ExpressionStatement","src":"6613:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"6549:5:0","parameters":{"id":716,"nodeType":"ParameterList","parameters":[{"constant":false,"id":715,"mutability":"mutable","name":"_v","nameLocation":"6571:2:0","nodeType":"VariableDeclaration","scope":728,"src":"6555:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":714,"name":"string","nodeType":"ElementaryTypeName","src":"6555:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6554:20:0"},"returnParameters":{"id":717,"nodeType":"ParameterList","parameters":[],"src":"6584:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":743,"nodeType":"FunctionDefinition","src":"6641:95:0","nodes":[],"body":{"id":742,"nodeType":"Block","src":"6685:51:0","nodes":[],"statements":[{"expression":{"id":734,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6695:9:0","subExpression":{"id":733,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6695:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":735,"nodeType":"ExpressionStatement","src":"6695:9:0"},{"expression":{"arguments":[{"id":739,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":730,"src":"6726:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":736,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6714:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":738,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6714:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":740,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6714:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":741,"nodeType":"ExpressionStatement","src":"6714:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"6650:5:0","parameters":{"id":731,"nodeType":"ParameterList","parameters":[{"constant":false,"id":730,"mutability":"mutable","name":"_v","nameLocation":"6672:2:0","nodeType":"VariableDeclaration","scope":743,"src":"6656:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":729,"name":"string","nodeType":"ElementaryTypeName","src":"6656:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6655:20:0"},"returnParameters":{"id":732,"nodeType":"ParameterList","parameters":[],"src":"6685:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":758,"nodeType":"FunctionDefinition","src":"6742:98:0","nodes":[],"body":{"id":757,"nodeType":"Block","src":"6788:52:0","nodes":[],"statements":[{"expression":{"id":749,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6798:9:0","subExpression":{"id":748,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6798:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":750,"nodeType":"ExpressionStatement","src":"6798:9:0"},{"expression":{"arguments":[{"id":754,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":745,"src":"6830:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":751,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6817:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":753,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":773,"src":"6817:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":755,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6817:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":756,"nodeType":"ExpressionStatement","src":"6817:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"6751:7:0","parameters":{"id":746,"nodeType":"ParameterList","parameters":[{"constant":false,"id":745,"mutability":"mutable","name":"_v","nameLocation":"6775:2:0","nodeType":"VariableDeclaration","scope":758,"src":"6759:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":744,"name":"string","nodeType":"ElementaryTypeName","src":"6759:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6758:20:0"},"returnParameters":{"id":747,"nodeType":"ParameterList","parameters":[],"src":"6788:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":773,"nodeType":"FunctionDefinition","src":"6846:97:0","nodes":[],"body":{"id":772,"nodeType":"Block","src":"6892:51:0","nodes":[],"statements":[{"expression":{"id":764,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6902:9:0","subExpression":{"id":763,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6902:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":765,"nodeType":"ExpressionStatement","src":"6902:9:0"},{"expression":{"arguments":[{"id":769,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":760,"src":"6933:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":766,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6921:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":768,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6921:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":770,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6921:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":771,"nodeType":"ExpressionStatement","src":"6921:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"6855:7:0","parameters":{"id":761,"nodeType":"ParameterList","parameters":[{"constant":false,"id":760,"mutability":"mutable","name":"_v","nameLocation":"6879:2:0","nodeType":"VariableDeclaration","scope":773,"src":"6863:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":759,"name":"string","nodeType":"ElementaryTypeName","src":"6863:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6862:20:0"},"returnParameters":{"id":762,"nodeType":"ParameterList","parameters":[],"src":"6892:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":785,"nodeType":"FunctionDefinition","src":"6949:84:0","nodes":[],"body":{"id":784,"nodeType":"Block","src":"7001:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":781,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":775,"src":"7023:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":778,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"7011:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":780,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"7011:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":782,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7011:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":783,"nodeType":"ExpressionStatement","src":"7011:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"6958:8:0","parameters":{"id":776,"nodeType":"ParameterList","parameters":[{"constant":false,"id":775,"mutability":"mutable","name":"_v","nameLocation":"6983:2:0","nodeType":"VariableDeclaration","scope":785,"src":"6967:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":774,"name":"string","nodeType":"ElementaryTypeName","src":"6967:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6966:20:0"},"returnParameters":{"id":777,"nodeType":"ParameterList","parameters":[],"src":"7001:0:0"},"scope":786,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[799],"contractKind":"contract","documentation":{"id":221,"nodeType":"StructuredDocumentation","src":"2997:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[786],"name":"ScriptExample","nameLocation":"3132:13:0","scope":969,"usedErrors":[]},{"id":799,"nodeType":"ContractDefinition","src":"7037:96:0","nodes":[{"id":788,"nodeType":"VariableDeclaration","src":"7059:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"7074:3:0","scope":799,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":787,"name":"uint256","nodeType":"ElementaryTypeName","src":"7059:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":798,"nodeType":"FunctionDefinition","src":"7084:47:0","nodes":[],"body":{"id":797,"nodeType":"Block","src":"7107:24:0","nodes":[],"statements":[{"expression":{"id":795,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":793,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":788,"src":"7117:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":794,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":790,"src":"7123:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"7117:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":796,"nodeType":"ExpressionStatement","src":"7117:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":791,"nodeType":"ParameterList","parameters":[{"constant":false,"id":790,"mutability":"mutable","name":"v","nameLocation":"7104:1:0","nodeType":"VariableDeclaration","scope":798,"src":"7096:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":789,"name":"uint256","nodeType":"ElementaryTypeName","src":"7096:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7095:11:0"},"returnParameters":{"id":792,"nodeType":"ParameterList","parameters":[],"src":"7107:0:0"},"scope":799,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[799],"name":"FooBar","nameLocation":"7046:6:0","scope":969,"usedErrors":[]},{"id":833,"nodeType":"ContractDefinition","src":"7135:281:0","nodes":[{"id":813,"nodeType":"VariableDeclaration","src":"7162:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7188:10:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":800,"name":"address","nodeType":"ElementaryTypeName","src":"7162:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":808,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7235:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":807,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7225:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":809,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7225:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":806,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7217:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":805,"name":"uint256","nodeType":"ElementaryTypeName","src":"7217:7:0","typeDescriptions":{}}},"id":810,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7217:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":804,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7209:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":803,"name":"uint160","nodeType":"ElementaryTypeName","src":"7209:7:0","typeDescriptions":{}}},"id":811,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7209:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":802,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":801,"name":"address","nodeType":"ElementaryTypeName","src":"7201:7:0","typeDescriptions":{}}},"id":812,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7201:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":819,"nodeType":"VariableDeclaration","src":"7262:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7283:2:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":815,"nodeType":"UserDefinedTypeName","pathNode":{"id":814,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7262:2:0"},"referencedDeclaration":83,"src":"7262:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":817,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":813,"src":"7291:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":816,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7288:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":818,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7288:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":832,"nodeType":"FunctionDefinition","src":"7309:105:0","nodes":[],"body":{"id":831,"nodeType":"Block","src":"7372:42:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":828,"name":"_addr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":821,"src":"7401:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":826,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":819,"src":"7389:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":827,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7389:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":829,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7389:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"functionReturnParameters":825,"id":830,"nodeType":"Return","src":"7382:25:0"}]},"functionSelector":"2d0335ab","implemented":true,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"7318:8:0","parameters":{"id":822,"nodeType":"ParameterList","parameters":[{"constant":false,"id":821,"mutability":"mutable","name":"_addr","nameLocation":"7335:5:0","nodeType":"VariableDeclaration","scope":832,"src":"7327:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":820,"name":"address","nodeType":"ElementaryTypeName","src":"7327:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7326:15:0"},"returnParameters":{"id":825,"nodeType":"ParameterList","parameters":[{"constant":false,"id":824,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":832,"src":"7363:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":823,"name":"uint256","nodeType":"ElementaryTypeName","src":"7363:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7362:9:0"},"scope":833,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"NonceGetter","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[833],"name":"NonceGetter","nameLocation":"7144:11:0","scope":969,"usedErrors":[]},{"id":852,"nodeType":"ContractDefinition","src":"7418:174:0","nodes":[{"id":835,"nodeType":"VariableDeclaration","src":"7448:18:0","nodes":[],"constant":false,"mutability":"mutable","name":"v","nameLocation":"7465:1:0","scope":852,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":834,"name":"uint256","nodeType":"ElementaryTypeName","src":"7448:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"id":843,"nodeType":"FunctionDefinition","src":"7473:36:0","nodes":[],"body":{"id":842,"nodeType":"Block","src":"7487:22:0","nodes":[],"statements":[{"expression":{"id":840,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":838,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7497:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"31","id":839,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7501:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"7497:5:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":841,"nodeType":"ExpressionStatement","src":"7497:5:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":836,"nodeType":"ParameterList","parameters":[],"src":"7484:2:0"},"returnParameters":{"id":837,"nodeType":"ParameterList","parameters":[],"src":"7487:0:0"},"scope":852,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":851,"nodeType":"FunctionDefinition","src":"7515:75:0","nodes":[],"body":{"id":850,"nodeType":"Block","src":"7565:25:0","nodes":[],"statements":[{"expression":{"id":848,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7582:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":847,"id":849,"nodeType":"Return","src":"7575:8:0"}]},"functionSelector":"20965255","implemented":true,"kind":"function","modifiers":[],"name":"getValue","nameLocation":"7524:8:0","parameters":{"id":844,"nodeType":"ParameterList","parameters":[],"src":"7532:2:0"},"returnParameters":{"id":847,"nodeType":"ParameterList","parameters":[{"constant":false,"id":846,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":851,"src":"7556:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":845,"name":"uint256","nodeType":"ElementaryTypeName","src":"7556:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7555:9:0"},"scope":852,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkedContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[852],"name":"ForkedContract","nameLocation":"7427:14:0","scope":969,"usedErrors":[]},{"id":968,"nodeType":"ContractDefinition","src":"7594:813:0","nodes":[{"id":866,"nodeType":"VariableDeclaration","src":"7620:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7646:10:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":853,"name":"address","nodeType":"ElementaryTypeName","src":"7620:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":861,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7693:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":860,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7683:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":862,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7683:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":859,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7675:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":858,"name":"uint256","nodeType":"ElementaryTypeName","src":"7675:7:0","typeDescriptions":{}}},"id":863,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7675:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":857,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7667:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":856,"name":"uint160","nodeType":"ElementaryTypeName","src":"7667:7:0","typeDescriptions":{}}},"id":864,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7667:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":855,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7659:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":854,"name":"address","nodeType":"ElementaryTypeName","src":"7659:7:0","typeDescriptions":{}}},"id":865,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7659:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":872,"nodeType":"VariableDeclaration","src":"7720:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7741:2:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":868,"nodeType":"UserDefinedTypeName","pathNode":{"id":867,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7720:2:0"},"referencedDeclaration":83,"src":"7720:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":870,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":866,"src":"7749:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":869,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7746:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":871,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7746:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":967,"nodeType":"FunctionDefinition","src":"7767:638:0","nodes":[],"body":{"id":966,"nodeType":"Block","src":"7791:614:0","nodes":[],"statements":[{"assignments":[876],"declarations":[{"constant":false,"id":876,"mutability":"mutable","name":"testAddr","nameLocation":"7809:8:0","nodeType":"VariableDeclaration","scope":966,"src":"7801:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":875,"name":"address","nodeType":"ElementaryTypeName","src":"7801:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":884,"initialValue":{"arguments":[{"arguments":[{"hexValue":"307831323334","id":881,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7836:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":880,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7828:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":879,"name":"uint160","nodeType":"ElementaryTypeName","src":"7828:7:0","typeDescriptions":{}}},"id":882,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7828:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":878,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7820:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":877,"name":"address","nodeType":"ElementaryTypeName","src":"7820:7:0","typeDescriptions":{}}},"id":883,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7820:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"7801:43:0"},{"assignments":[887],"declarations":[{"constant":false,"id":887,"mutability":"mutable","name":"fc","nameLocation":"7869:2:0","nodeType":"VariableDeclaration","scope":966,"src":"7854:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"},"typeName":{"id":886,"nodeType":"UserDefinedTypeName","pathNode":{"id":885,"name":"ForkedContract","nodeType":"IdentifierPath","referencedDeclaration":852,"src":"7854:14:0"},"referencedDeclaration":852,"src":"7854:14:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"visibility":"internal"}],"id":891,"initialValue":{"arguments":[{"id":889,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7889:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":888,"name":"ForkedContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":852,"src":"7874:14:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ForkedContract_$852_$","typeString":"type(contract ForkedContract)"}},"id":890,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7874:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"nodeType":"VariableDeclarationStatement","src":"7854:44:0"},{"expression":{"arguments":[{"hexValue":"666f726b31","id":895,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7929:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},"value":"fork1"},{"hexValue":"3132333435","id":896,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7938:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"}],"expression":{"id":892,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7909:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":894,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"7909:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":897,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7909:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":898,"nodeType":"ExpressionStatement","src":"7909:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":905,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":902,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7974:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":900,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7962:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":901,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7962:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":903,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7962:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3132333435","id":904,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7987:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"},"src":"7962:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":906,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7994:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":899,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"7954:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":907,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7954:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":908,"nodeType":"ExpressionStatement","src":"7954:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":914,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":910,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8036:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":911,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8036:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":912,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8036:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31","id":913,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8053:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"8036:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652031","id":915,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8056:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""},"value":"value should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""}],"id":909,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8028:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":916,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8028:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":917,"nodeType":"ExpressionStatement","src":"8028:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":925,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":919,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8094:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":920,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8094:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"31","id":923,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8122:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"}],"id":922,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8114:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":921,"name":"uint256","nodeType":"ElementaryTypeName","src":"8114:7:0","typeDescriptions":{}}},"id":924,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8114:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8094:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652031","id":926,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8126:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""},"value":"balance should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""}],"id":918,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8086:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":927,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8086:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":928,"nodeType":"ExpressionStatement","src":"8086:62:0"},{"expression":{"arguments":[{"hexValue":"666f726b32","id":932,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8179:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},"value":"fork2"},{"hexValue":"3233343536","id":933,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8188:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"}],"expression":{"id":929,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8159:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":931,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"8159:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":934,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8159:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":935,"nodeType":"ExpressionStatement","src":"8159:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":939,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8224:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":937,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8212:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":938,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"8212:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":940,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8212:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3233343536","id":941,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8237:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"},"src":"8212:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":943,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8244:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":936,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8204:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":944,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8204:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":945,"nodeType":"ExpressionStatement","src":"8204:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":951,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":947,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8286:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":948,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8286:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":949,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8286:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"32","id":950,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8303:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"src":"8286:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652032","id":952,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8306:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""},"value":"value should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""}],"id":946,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8278:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":953,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8278:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":954,"nodeType":"ExpressionStatement","src":"8278:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":962,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":956,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8344:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":957,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8344:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"32","id":960,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8372:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"}],"id":959,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8364:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":958,"name":"uint256","nodeType":"ElementaryTypeName","src":"8364:7:0","typeDescriptions":{}}},"id":961,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8364:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8344:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652032","id":963,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8376:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""},"value":"balance should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""}],"id":955,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8336:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":964,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8336:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":965,"nodeType":"ExpressionStatement","src":"8336:62:0"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"7776:3:0","parameters":{"id":873,"nodeType":"ParameterList","parameters":[],"src":"7779:2:0"},"returnParameters":{"id":874,"nodeType":"ParameterList","parameters":[],"src":"7791:0:0"},"scope":968,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkTester","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[968],"name":"ForkTester","nameLocation":"7603:10:0","scope":969,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ForkedContract.json b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ForkedContract.json new file mode 100644 index 0000000000000..b2c69de3ffbc1 --- /dev/null +++ b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ForkedContract.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"getValue","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"}],"bytecode":{"object":"0x6080604052348015600f57600080fd5b506001600055604f8060226000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632096525514602d575b600080fd5b60005460405190815260200160405180910390f3fea164736f6c634300080f000a","sourceMap":"7418:174:0:-:0;;;7473:36;;;;;;;;;-1:-1:-1;7501:1:0;7497;:5;7418:174;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052348015600f57600080fd5b506004361060285760003560e01c80632096525514602d575b600080fd5b60005460405190815260200160405180910390f3fea164736f6c634300080f000a","sourceMap":"7418:174:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7515:75;7556:7;7582:1;7515:75;;160:25:1;;;148:2;133:18;7515:75:0;;;;;;","linkReferences":{}},"methodIdentifiers":{"getValue()":"20965255"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"getValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"ForkedContract\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28\",\"dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"stateMutability":"view","type":"function","name":"getValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"ForkedContract"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da","urls":["bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28","dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[{"astId":835,"contract":"scripts/ScriptExample.s.sol:ForkedContract","label":"v","offset":0,"slot":"0","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":969,"exportedSymbols":{"FooBar":[799],"ForkTester":[968],"ForkedContract":[852],"NonceGetter":[833],"ScriptExample":[786],"Vm":[83],"console":[220]},"nodeType":"SourceUnit","src":"32:8375:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":83,"nodeType":"ContractDefinition","src":"120:969:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":83,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":61,"nodeType":"FunctionDefinition","src":"739:108:0","nodes":[],"functionSelector":"3ebf73b4","implemented":false,"kind":"function","modifiers":[],"name":"getDeployedCode","nameLocation":"748:15:0","parameters":{"id":57,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56,"mutability":"mutable","name":"artifactPath","nameLocation":"780:12:0","nodeType":"VariableDeclaration","scope":61,"src":"764:28:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":55,"name":"string","nodeType":"ElementaryTypeName","src":"764:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"763:30:0"},"returnParameters":{"id":60,"nodeType":"ParameterList","parameters":[{"constant":false,"id":59,"mutability":"mutable","name":"runtimeBytecode","nameLocation":"830:15:0","nodeType":"VariableDeclaration","scope":61,"src":"817:28:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":58,"name":"bytes","nodeType":"ElementaryTypeName","src":"817:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"816:30:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":68,"nodeType":"FunctionDefinition","src":"852:74:0","nodes":[],"functionSelector":"b4d6c782","implemented":false,"kind":"function","modifiers":[],"name":"etch","nameLocation":"861:4:0","parameters":{"id":66,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"target","nameLocation":"874:6:0","nodeType":"VariableDeclaration","scope":68,"src":"866:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":62,"name":"address","nodeType":"ElementaryTypeName","src":"866:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":65,"mutability":"mutable","name":"newRuntimeBytecode","nameLocation":"897:18:0","nodeType":"VariableDeclaration","scope":68,"src":"882:33:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":64,"name":"bytes","nodeType":"ElementaryTypeName","src":"882:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"865:51:0"},"returnParameters":{"id":67,"nodeType":"ParameterList","parameters":[],"src":"925:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":73,"nodeType":"FunctionDefinition","src":"931:51:0","nodes":[],"functionSelector":"ea060291","implemented":false,"kind":"function","modifiers":[],"name":"allowCheatcodes","nameLocation":"940:15:0","parameters":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"account","nameLocation":"964:7:0","nodeType":"VariableDeclaration","scope":73,"src":"956:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":69,"name":"address","nodeType":"ElementaryTypeName","src":"956:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"955:17:0"},"returnParameters":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"981:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":82,"nodeType":"FunctionDefinition","src":"987:100:0","nodes":[],"functionSelector":"71ee464d","implemented":false,"kind":"function","modifiers":[],"name":"createSelectFork","nameLocation":"996:16:0","parameters":{"id":78,"nodeType":"ParameterList","parameters":[{"constant":false,"id":75,"mutability":"mutable","name":"forkName","nameLocation":"1029:8:0","nodeType":"VariableDeclaration","scope":82,"src":"1013:24:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":74,"name":"string","nodeType":"ElementaryTypeName","src":"1013:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":77,"mutability":"mutable","name":"blockNumber","nameLocation":"1047:11:0","nodeType":"VariableDeclaration","scope":82,"src":"1039:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":76,"name":"uint256","nodeType":"ElementaryTypeName","src":"1039:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1012:47:0"},"returnParameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":82,"src":"1078:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":79,"name":"uint256","nodeType":"ElementaryTypeName","src":"1078:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1077:9:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[83],"name":"Vm","nameLocation":"130:2:0","scope":969,"usedErrors":[]},{"id":220,"nodeType":"ContractDefinition","src":"1144:1851:0","nodes":[{"id":89,"nodeType":"VariableDeclaration","src":"1166:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"1183:15:0","scope":220,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":84,"name":"address","nodeType":"ElementaryTypeName","src":"1166:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":87,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1209:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":86,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":85,"name":"address","nodeType":"ElementaryTypeName","src":"1201:7:0","typeDescriptions":{}}},"id":88,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1201:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1259:235:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1432:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1451:37:0","statements":[{"nodeType":"YulAssignment","src":"1465:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1474:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1465:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":95,"isOffset":false,"isSlot":false,"src":"1474:4:0","valueSize":1},{"declaration":102,"isOffset":false,"isSlot":false,"src":"1465:5:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1442:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"1268:25:0","parameters":{"id":96,"nodeType":"ParameterList","parameters":[{"constant":false,"id":95,"mutability":"mutable","name":"fnIn","nameLocation":"1331:4:0","nodeType":"VariableDeclaration","scope":106,"src":"1294:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":94,"nodeType":"FunctionTypeName","parameterTypes":{"id":92,"nodeType":"ParameterList","parameters":[{"constant":false,"id":91,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":94,"src":"1303:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":90,"name":"bytes","nodeType":"ElementaryTypeName","src":"1303:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1302:14:0"},"returnParameterTypes":{"id":93,"nodeType":"ParameterList","parameters":[],"src":"1331:0:0"},"src":"1294:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"1293:43:0"},"returnParameters":{"id":103,"nodeType":"ParameterList","parameters":[{"constant":false,"id":102,"mutability":"mutable","name":"fnOut","nameLocation":"1421:5:0","nodeType":"VariableDeclaration","scope":106,"src":"1384:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":101,"nodeType":"FunctionTypeName","parameterTypes":{"id":99,"nodeType":"ParameterList","parameters":[{"constant":false,"id":98,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":101,"src":"1393:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":97,"name":"bytes","nodeType":"ElementaryTypeName","src":"1393:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1392:14:0"},"returnParameterTypes":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"1421:0:0"},"src":"1384:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1383:44:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":118,"nodeType":"FunctionDefinition","src":"1500:133:0","nodes":[],"body":{"id":117,"nodeType":"Block","src":"1561:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":114,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1618:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":112,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":134,"src":"1597:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":111,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1571:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":113,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":115,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":116,"nodeType":"ExpressionStatement","src":"1571:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1509:15:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"payload","nameLocation":"1538:7:0","nodeType":"VariableDeclaration","scope":118,"src":"1525:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":107,"name":"bytes","nodeType":"ElementaryTypeName","src":"1525:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1524:22:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1561:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":134,"nodeType":"FunctionDefinition","src":"1639:380:0","nodes":[],"body":{"id":133,"nodeType":"Block","src":"1703:316:0","nodes":[],"statements":[{"assignments":[124],"declarations":[{"constant":false,"id":124,"mutability":"mutable","name":"payloadLength","nameLocation":"1721:13:0","nodeType":"VariableDeclaration","scope":133,"src":"1713:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":123,"name":"uint256","nodeType":"ElementaryTypeName","src":"1713:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":127,"initialValue":{"expression":{"id":125,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":120,"src":"1737:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":126,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1737:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1713:38:0"},{"assignments":[129],"declarations":[{"constant":false,"id":129,"mutability":"mutable","name":"consoleAddress","nameLocation":"1769:14:0","nodeType":"VariableDeclaration","scope":133,"src":"1761:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":128,"name":"address","nodeType":"ElementaryTypeName","src":"1761:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":131,"initialValue":{"id":130,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":89,"src":"1786:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1761:40:0"},{"AST":{"nodeType":"YulBlock","src":"1863:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1877:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1901:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1910:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1897:3:0"},"nodeType":"YulFunctionCall","src":"1897:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1881:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1926:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1946:3:0"},"nodeType":"YulFunctionCall","src":"1946:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1953:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1969:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1983:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1998:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"2001:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1935:10:0"},"nodeType":"YulFunctionCall","src":"1935:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1930:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":129,"isOffset":false,"isSlot":false,"src":"1953:14:0","valueSize":1},{"declaration":120,"isOffset":false,"isSlot":false,"src":"1901:7:0","valueSize":1},{"declaration":124,"isOffset":false,"isSlot":false,"src":"1983:13:0","valueSize":1}],"id":132,"nodeType":"InlineAssembly","src":"1854:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1648:19:0","parameters":{"id":121,"nodeType":"ParameterList","parameters":[{"constant":false,"id":120,"mutability":"mutable","name":"payload","nameLocation":"1681:7:0","nodeType":"VariableDeclaration","scope":134,"src":"1668:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":119,"name":"bytes","nodeType":"ElementaryTypeName","src":"1668:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1667:22:0"},"returnParameters":{"id":122,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":220,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":148,"nodeType":"FunctionDefinition","src":"2025:164:0","nodes":[],"body":{"id":147,"nodeType":"Block","src":"2070:119:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":142,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2120:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":143,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":136,"src":"2135:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":140,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2096:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":141,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2096: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":144,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2096:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":139,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2080:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":145,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2080:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":146,"nodeType":"ExpressionStatement","src":"2080:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2034:3:0","parameters":{"id":137,"nodeType":"ParameterList","parameters":[{"constant":false,"id":136,"mutability":"mutable","name":"p0","nameLocation":"2052:2:0","nodeType":"VariableDeclaration","scope":148,"src":"2038:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":135,"name":"string","nodeType":"ElementaryTypeName","src":"2038:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2037:18:0"},"returnParameters":{"id":138,"nodeType":"ParameterList","parameters":[],"src":"2070:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":165,"nodeType":"FunctionDefinition","src":"2195:182:0","nodes":[],"body":{"id":164,"nodeType":"Block","src":"2249:128:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":158,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2299:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":159,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":150,"src":"2319:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":160,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":152,"src":"2323:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":156,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2275:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":157,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2275: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":161,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2275:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":155,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2259:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":162,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2259:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":163,"nodeType":"ExpressionStatement","src":"2259:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2204:3:0","parameters":{"id":153,"nodeType":"ParameterList","parameters":[{"constant":false,"id":150,"mutability":"mutable","name":"p0","nameLocation":"2222:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2208:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":149,"name":"string","nodeType":"ElementaryTypeName","src":"2208:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":152,"mutability":"mutable","name":"p1","nameLocation":"2231:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2226:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":151,"name":"bool","nodeType":"ElementaryTypeName","src":"2226:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"2207:27:0"},"returnParameters":{"id":154,"nodeType":"ParameterList","parameters":[],"src":"2249:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":182,"nodeType":"FunctionDefinition","src":"2383:188:0","nodes":[],"body":{"id":181,"nodeType":"Block","src":"2440:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":175,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2490:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":176,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":167,"src":"2513:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":177,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":169,"src":"2517:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":173,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2466:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":174,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2466: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":178,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2466:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":172,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2450:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":179,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2450:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":180,"nodeType":"ExpressionStatement","src":"2450:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2392:3:0","parameters":{"id":170,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"mutability":"mutable","name":"p0","nameLocation":"2410:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2396:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":166,"name":"string","nodeType":"ElementaryTypeName","src":"2396:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":169,"mutability":"mutable","name":"p1","nameLocation":"2422:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2414:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":168,"name":"uint256","nodeType":"ElementaryTypeName","src":"2414:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2395:30:0"},"returnParameters":{"id":171,"nodeType":"ParameterList","parameters":[],"src":"2440:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":199,"nodeType":"FunctionDefinition","src":"2577:188:0","nodes":[],"body":{"id":198,"nodeType":"Block","src":"2634:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":192,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2684:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":193,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":184,"src":"2707:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":194,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"2711:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":190,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2660:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":191,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2660: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":195,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2660:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":189,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2644:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":196,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2644:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":197,"nodeType":"ExpressionStatement","src":"2644:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2586:3:0","parameters":{"id":187,"nodeType":"ParameterList","parameters":[{"constant":false,"id":184,"mutability":"mutable","name":"p0","nameLocation":"2604:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2590:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":183,"name":"string","nodeType":"ElementaryTypeName","src":"2590:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":186,"mutability":"mutable","name":"p1","nameLocation":"2616:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2608:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":185,"name":"address","nodeType":"ElementaryTypeName","src":"2608:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2589:30:0"},"returnParameters":{"id":188,"nodeType":"ParameterList","parameters":[],"src":"2634:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":219,"nodeType":"FunctionDefinition","src":"2771:222:0","nodes":[],"body":{"id":218,"nodeType":"Block","src":"2852:141:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":211,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2902:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":212,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":201,"src":"2931:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":213,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":203,"src":"2935:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":214,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":205,"src":"2939:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":209,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2878:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":210,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2878: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":215,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2878:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":208,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2862:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":216,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2862:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":217,"nodeType":"ExpressionStatement","src":"2862:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2780:3:0","parameters":{"id":206,"nodeType":"ParameterList","parameters":[{"constant":false,"id":201,"mutability":"mutable","name":"p0","nameLocation":"2798:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2784:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":200,"name":"string","nodeType":"ElementaryTypeName","src":"2784:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":203,"mutability":"mutable","name":"p1","nameLocation":"2816:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2802:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":202,"name":"string","nodeType":"ElementaryTypeName","src":"2802:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":205,"mutability":"mutable","name":"p2","nameLocation":"2834:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2820:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":204,"name":"string","nodeType":"ElementaryTypeName","src":"2820:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2783:54:0"},"returnParameters":{"id":207,"nodeType":"ParameterList","parameters":[],"src":"2852:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[220],"name":"console","nameLocation":"1152:7:0","scope":969,"usedErrors":[]},{"id":786,"nodeType":"ContractDefinition","src":"3123:3912:0","nodes":[{"id":235,"nodeType":"VariableDeclaration","src":"3152:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"3178:10:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":222,"name":"address","nodeType":"ElementaryTypeName","src":"3152:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3225:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":229,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"3215:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":231,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3215:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":228,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3207:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":227,"name":"uint256","nodeType":"ElementaryTypeName","src":"3207:7:0","typeDescriptions":{}}},"id":232,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3207:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":226,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3199:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":225,"name":"uint160","nodeType":"ElementaryTypeName","src":"3199:7:0","typeDescriptions":{}}},"id":233,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3199:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":224,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3191:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":223,"name":"address","nodeType":"ElementaryTypeName","src":"3191:7:0","typeDescriptions":{}}},"id":234,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3191:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":241,"nodeType":"VariableDeclaration","src":"3252:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"3273:2:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":237,"nodeType":"UserDefinedTypeName","pathNode":{"id":236,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"3252:2:0"},"referencedDeclaration":83,"src":"3252:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":239,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":235,"src":"3281:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":238,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"3278:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":240,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3278:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":243,"nodeType":"VariableDeclaration","src":"3356:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"3371:7:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":242,"name":"uint256","nodeType":"ElementaryTypeName","src":"3356:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":456,"nodeType":"FunctionDefinition","src":"3468:1428:0","nodes":[],"body":{"id":455,"nodeType":"Block","src":"3490:1406:0","nodes":[],"statements":[{"assignments":[248],"declarations":[{"constant":false,"id":248,"mutability":"mutable","name":"x","nameLocation":"3505:1:0","nodeType":"VariableDeclaration","scope":455,"src":"3500:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":247,"name":"bool","nodeType":"ElementaryTypeName","src":"3500:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":254,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":251,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3518:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":252,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"3534:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":249,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3509:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":250,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"3509:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"3500:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":258,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3562:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":259,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":248,"src":"3585:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":255,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3550:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":257,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":165,"src":"3550:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":260,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3550:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":261,"nodeType":"ExpressionStatement","src":"3550:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":265,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3610:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":268,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3635:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":267,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3627:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":266,"name":"address","nodeType":"ElementaryTypeName","src":"3627:7:0","typeDescriptions":{}}},"id":269,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3627:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":262,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3598:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3598:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3598:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":271,"nodeType":"ExpressionStatement","src":"3598:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":275,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3663:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":280,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3701:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":279,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3693:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":278,"name":"address","nodeType":"ElementaryTypeName","src":"3693:7:0","typeDescriptions":{}}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3693:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":276,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3681:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3681:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":282,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":272,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3651:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":274,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3651:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":283,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3651:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":284,"nodeType":"ExpressionStatement","src":"3651:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":288,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3730:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":291,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3753:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3753:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":290,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3745:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":289,"name":"address","nodeType":"ElementaryTypeName","src":"3745:7:0","typeDescriptions":{}}},"id":293,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3745:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":285,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3718:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3718:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3718:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":295,"nodeType":"ExpressionStatement","src":"3718:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":299,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3787:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":304,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3823:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":305,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3823:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":303,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3815:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":302,"name":"address","nodeType":"ElementaryTypeName","src":"3815:7:0","typeDescriptions":{}}},"id":306,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3815:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":300,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3803:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3803:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3803:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":296,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3775:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":298,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3775:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":308,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3775:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":309,"nodeType":"ExpressionStatement","src":"3775:61:0"},{"assignments":[311],"declarations":[{"constant":false,"id":311,"mutability":"mutable","name":"json","nameLocation":"3861:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3847:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":310,"name":"string","nodeType":"ElementaryTypeName","src":"3847:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":313,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3868:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3847:55:0"},{"assignments":[318],"declarations":[{"constant":false,"id":318,"mutability":"mutable","name":"keys","nameLocation":"3928:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3912:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":316,"name":"string","nodeType":"ElementaryTypeName","src":"3912:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":317,"nodeType":"ArrayTypeName","src":"3912:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":324,"initialValue":{"arguments":[{"id":321,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":311,"src":"3952:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3958:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":319,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3935:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":320,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3935:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":323,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3935:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3912:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":328,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3995:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":329,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4003:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":331,"indexExpression":{"hexValue":"30","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4008:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4003:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":332,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4012:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":334,"indexExpression":{"hexValue":"31","id":333,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4017:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4012:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":325,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3983:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":327,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":219,"src":"3983:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3983:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":336,"nodeType":"ExpressionStatement","src":"3983:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":340,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4042:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":337,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4031:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":339,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4031:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4031:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":342,"nodeType":"ExpressionStatement","src":"4031:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":350,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4098:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4090:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":348,"name":"uint160","nodeType":"ElementaryTypeName","src":"4090:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4090:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":347,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4082:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":346,"name":"address","nodeType":"ElementaryTypeName","src":"4082:7:0","typeDescriptions":{}}},"id":352,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4082:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":343,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4068:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":345,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"4068:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":353,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4068:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":354,"nodeType":"ExpressionStatement","src":"4068:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":358,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4126:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":355,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4115:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":357,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4115:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":359,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4115:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":360,"nodeType":"ExpressionStatement","src":"4115:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":364,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4163:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":367,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4198:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":368,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"4198:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":366,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4190:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":365,"name":"address","nodeType":"ElementaryTypeName","src":"4190:7:0","typeDescriptions":{}}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4190:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":361,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4151:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4151:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":370,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4151:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":371,"nodeType":"ExpressionStatement","src":"4151:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":375,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4232:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":378,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4270:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":377,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4262:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":376,"name":"address","nodeType":"ElementaryTypeName","src":"4262:7:0","typeDescriptions":{}}},"id":379,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4262:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":372,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4220:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":374,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4220:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":380,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4220:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":381,"nodeType":"ExpressionStatement","src":"4220:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4297:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":382,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4286:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4286:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":386,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4286:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":387,"nodeType":"ExpressionStatement","src":"4286:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4322:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":390,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"4322:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":391,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4322:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":392,"nodeType":"ExpressionStatement","src":"4322:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":396,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4357:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":393,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4346:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4346:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":397,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4346:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":398,"nodeType":"ExpressionStatement","src":"4346:33:0"},{"assignments":[400],"declarations":[{"constant":false,"id":400,"mutability":"mutable","name":"tmpNonceGetter","nameLocation":"4480:14:0","nodeType":"VariableDeclaration","scope":455,"src":"4472:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":399,"name":"address","nodeType":"ElementaryTypeName","src":"4472:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":413,"initialValue":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"74656d70206e6f6e6365207465737420676574746572","id":408,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4531:24:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""},"value":"temp nonce test getter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""}],"id":407,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"4521:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":409,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4521:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":406,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4513:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":405,"name":"uint256","nodeType":"ElementaryTypeName","src":"4513:7:0","typeDescriptions":{}}},"id":410,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4513:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":404,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4505:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":403,"name":"uint160","nodeType":"ElementaryTypeName","src":"4505:7:0","typeDescriptions":{}}},"id":411,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4505:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":402,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4497:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":401,"name":"address","nodeType":"ElementaryTypeName","src":"4497:7:0","typeDescriptions":{}}},"id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4497:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"4472:87:0"},{"expression":{"arguments":[{"id":417,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4577:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"hexValue":"5363726970744578616d706c652e732e736f6c3a4e6f6e6365476574746572","id":420,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4612:33:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""},"value":"ScriptExample.s.sol:NonceGetter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""}],"expression":{"id":418,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4593:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getDeployedCode","nodeType":"MemberAccess","referencedDeclaration":61,"src":"4593:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) view external returns (bytes memory)"}},"id":421,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4593:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":414,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4569:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":416,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"etch","nodeType":"MemberAccess","referencedDeclaration":68,"src":"4569:7:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory) external"}},"id":422,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4569:78:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":423,"nodeType":"ExpressionStatement","src":"4569:78:0"},{"expression":{"arguments":[{"id":427,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4676:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":424,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4657:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":426,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"allowCheatcodes","nodeType":"MemberAccess","referencedDeclaration":73,"src":"4657:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":428,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":429,"nodeType":"ExpressionStatement","src":"4657:34:0"},{"assignments":[431],"declarations":[{"constant":false,"id":431,"mutability":"mutable","name":"v","nameLocation":"4709:1:0","nodeType":"VariableDeclaration","scope":455,"src":"4701:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":430,"name":"uint256","nodeType":"ElementaryTypeName","src":"4701:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":441,"initialValue":{"arguments":[{"arguments":[{"id":438,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4758:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":437,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4750:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":436,"name":"address","nodeType":"ElementaryTypeName","src":"4750:7:0","typeDescriptions":{}}},"id":439,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4750:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"arguments":[{"id":433,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4725:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":432,"name":"NonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":833,"src":"4713:11:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_NonceGetter_$833_$","typeString":"type(contract NonceGetter)"}},"id":434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_NonceGetter_$833","typeString":"contract NonceGetter"}},"id":435,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":832,"src":"4713:36:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view external returns (uint256)"}},"id":440,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"4701:63:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683a","id":445,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4786:68:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},"value":"nonce from nonce getter, no explicit access required with vm.etch:"},{"id":446,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":431,"src":"4856:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":442,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4774:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":444,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"4774:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4774:84:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":448,"nodeType":"ExpressionStatement","src":"4774:84:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":452,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4881:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":449,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4869:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":451,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"4869:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4869:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":454,"nodeType":"ExpressionStatement","src":"4869:20:0"}]},"documentation":{"id":244,"nodeType":"StructuredDocumentation","src":"3385:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"3477:3:0","parameters":{"id":245,"nodeType":"ParameterList","parameters":[],"src":"3480:2:0"},"returnParameters":{"id":246,"nodeType":"ParameterList","parameters":[],"src":"3490:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":689,"nodeType":"FunctionDefinition","src":"4963:1333:0","nodes":[],"body":{"id":688,"nodeType":"Block","src":"4994:1302:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":463,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5016:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":470,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5059:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":469,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5051:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":468,"name":"address","nodeType":"ElementaryTypeName","src":"5051:7:0","typeDescriptions":{}}},"id":471,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5051:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":466,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5039:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":467,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5039:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5039:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":465,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5031:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":464,"name":"uint256","nodeType":"ElementaryTypeName","src":"5031:7:0","typeDescriptions":{}}},"id":473,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5031:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":460,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5004:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":462,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"5004:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":474,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5004:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":475,"nodeType":"ExpressionStatement","src":"5004:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":479,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5090:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":476,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5078:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":478,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5078:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":480,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5078:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":481,"nodeType":"ExpressionStatement","src":"5078:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":482,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5117:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5117:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":485,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5117:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":486,"nodeType":"ExpressionStatement","src":"5117:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":490,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5152:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":487,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5141:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":489,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5141:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":491,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5141:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":492,"nodeType":"ExpressionStatement","src":"5141:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":496,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5188:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":493,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5177:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5177:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":497,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5177:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":498,"nodeType":"ExpressionStatement","src":"5177:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":502,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5226:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":499,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5214:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5214:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":503,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5214:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":504,"nodeType":"ExpressionStatement","src":"5214:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5291:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":511,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5283:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":510,"name":"uint160","nodeType":"ElementaryTypeName","src":"5283:7:0","typeDescriptions":{}}},"id":513,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5283:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5275:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":508,"name":"address","nodeType":"ElementaryTypeName","src":"5275:7:0","typeDescriptions":{}}},"id":514,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5275:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":505,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5257:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":507,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5257:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":515,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5257:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":516,"nodeType":"ExpressionStatement","src":"5257:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":520,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5323:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":517,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5312:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":519,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5312:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":521,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5312:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":522,"nodeType":"ExpressionStatement","src":"5312:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":526,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5362:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":523,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5351:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":525,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5351:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5351:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":528,"nodeType":"ExpressionStatement","src":"5351:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":532,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5404:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":529,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5390:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":531,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":785,"src":"5390:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":533,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5390:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":534,"nodeType":"ExpressionStatement","src":"5390:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":535,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5431:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":537,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5431:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":538,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5431:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":539,"nodeType":"ExpressionStatement","src":"5431:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":543,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5470:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":540,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5459:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":542,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5459:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5459:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":545,"nodeType":"ExpressionStatement","src":"5459:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":549,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5511:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":546,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5499:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5499:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":550,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5499:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":551,"nodeType":"ExpressionStatement","src":"5499:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":559,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5572:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":558,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5564:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":557,"name":"uint160","nodeType":"ElementaryTypeName","src":"5564:7:0","typeDescriptions":{}}},"id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5564:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":556,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5556:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":555,"name":"address","nodeType":"ElementaryTypeName","src":"5556:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5556:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":552,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5538:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":554,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5538:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5538:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":563,"nodeType":"ExpressionStatement","src":"5538:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":567,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5604:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":564,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5591:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":758,"src":"5591:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":568,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5591:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":569,"nodeType":"ExpressionStatement","src":"5591:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":570,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5623:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5623:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5623:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"5623:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5664:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5652:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5652:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5652:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"5652:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":588,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5725:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":587,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5717:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":586,"name":"uint160","nodeType":"ElementaryTypeName","src":"5717:7:0","typeDescriptions":{}}},"id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5717:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":585,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5709:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":584,"name":"address","nodeType":"ElementaryTypeName","src":"5709:7:0","typeDescriptions":{}}},"id":590,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5709:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5696:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5696:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":591,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5696:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":592,"nodeType":"ExpressionStatement","src":"5696:40:0"},{"assignments":[595],"declarations":[{"constant":false,"id":595,"mutability":"mutable","name":"x","nameLocation":"5753:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5746:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":594,"nodeType":"UserDefinedTypeName","pathNode":{"id":593,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5746:6:0"},"referencedDeclaration":799,"src":"5746:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":601,"initialValue":{"arguments":[{"hexValue":"31323334","id":599,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5768:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":598,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5757:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":597,"nodeType":"UserDefinedTypeName","pathNode":{"id":596,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5761:6:0"},"referencedDeclaration":799,"src":"5761:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":600,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5757:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5746:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":607,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":603,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":595,"src":"5791:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"5791:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5791:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":606,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5802:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"5791:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323334","id":608,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5808:35:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""},"value":"FooBar: foo in create is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""}],"id":602,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"5783:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":609,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5783:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":610,"nodeType":"ExpressionStatement","src":"5783:61:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":614,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5867:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":611,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5855:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":613,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5855:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":615,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5855:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":616,"nodeType":"ExpressionStatement","src":"5855:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5917:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":623,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5909:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":622,"name":"uint160","nodeType":"ElementaryTypeName","src":"5909:7:0","typeDescriptions":{}}},"id":625,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5909:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":621,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5901:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":620,"name":"address","nodeType":"ElementaryTypeName","src":"5901:7:0","typeDescriptions":{}}},"id":626,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5901:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":617,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5888:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":627,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5888:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":628,"nodeType":"ExpressionStatement","src":"5888:38:0"},{"assignments":[631],"declarations":[{"constant":false,"id":631,"mutability":"mutable","name":"y","nameLocation":"5943:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5936:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":630,"nodeType":"UserDefinedTypeName","pathNode":{"id":629,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5936:6:0"},"referencedDeclaration":799,"src":"5936:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":645,"initialValue":{"arguments":[{"hexValue":"31323334","id":643,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5986:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":634,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5947:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":633,"nodeType":"UserDefinedTypeName","pathNode":{"id":632,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5951:6:0"},"referencedDeclaration":799,"src":"5951:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":642,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":639,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5980:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":638,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5972:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":637,"name":"uint256","nodeType":"ElementaryTypeName","src":"5972:7:0","typeDescriptions":{}}},"id":640,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5972:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":636,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5964:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":635,"name":"bytes32","nodeType":"ElementaryTypeName","src":"5964:7:0","typeDescriptions":{}}},"id":641,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5964:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"5947:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":644,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5947:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5936:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":651,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":647,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":631,"src":"6009:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":648,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"6009:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":649,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6009:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":650,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6020:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"6009:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e2063726561746532206973206e6f742031323334","id":652,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6026:36:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""},"value":"FooBar: foo in create2 is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""}],"id":646,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"6001:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6001:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":654,"nodeType":"ExpressionStatement","src":"6001:62:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":658,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6085:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":655,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6073:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":657,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6073:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":659,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6073:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":660,"nodeType":"ExpressionStatement","src":"6073:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":661,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6177:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"6177:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":664,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6177:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":665,"nodeType":"ExpressionStatement","src":"6177:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":669,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6212:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":668,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"6201:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":667,"nodeType":"UserDefinedTypeName","pathNode":{"id":666,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"6205:6:0"},"referencedDeclaration":799,"src":"6205:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":670,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6201:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":671,"nodeType":"ExpressionStatement","src":"6201:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":675,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6240:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":682,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6281:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":681,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6273:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":680,"name":"address","nodeType":"ElementaryTypeName","src":"6273:7:0","typeDescriptions":{}}},"id":683,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6273:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":678,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6261:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"6261:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":684,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6261:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":677,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6253:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":676,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:0","typeDescriptions":{}}},"id":685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6253:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":672,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6228:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":674,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"6228:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":686,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6228:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":687,"nodeType":"ExpressionStatement","src":"6228:61:0"}]},"documentation":{"id":457,"nodeType":"StructuredDocumentation","src":"4902:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"4972:12:0","parameters":{"id":458,"nodeType":"ParameterList","parameters":[],"src":"4984:2:0"},"returnParameters":{"id":459,"nodeType":"ParameterList","parameters":[],"src":"4994:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":713,"nodeType":"FunctionDefinition","src":"6391:143:0","nodes":[],"body":{"id":712,"nodeType":"Block","src":"6440:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":698,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":692,"src":"6462:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":695,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6450:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":697,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6450:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":699,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6450:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":700,"nodeType":"ExpressionStatement","src":"6450:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6487:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":707,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"6515:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":708,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"6515:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":706,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6507:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":705,"name":"address","nodeType":"ElementaryTypeName","src":"6507:7:0","typeDescriptions":{}}},"id":709,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6507:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":701,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6475:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"6475:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":710,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6475:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":711,"nodeType":"ExpressionStatement","src":"6475:52:0"}]},"documentation":{"id":690,"nodeType":"StructuredDocumentation","src":"6302:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"6400:5:0","parameters":{"id":693,"nodeType":"ParameterList","parameters":[{"constant":false,"id":692,"mutability":"mutable","name":"_v","nameLocation":"6422:2:0","nodeType":"VariableDeclaration","scope":713,"src":"6406:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":691,"name":"string","nodeType":"ElementaryTypeName","src":"6406:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6405:20:0"},"returnParameters":{"id":694,"nodeType":"ParameterList","parameters":[],"src":"6440:0:0"},"scope":786,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":728,"nodeType":"FunctionDefinition","src":"6540:95:0","nodes":[],"body":{"id":727,"nodeType":"Block","src":"6584:51:0","nodes":[],"statements":[{"expression":{"id":719,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6594:9:0","subExpression":{"id":718,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6594:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":720,"nodeType":"ExpressionStatement","src":"6594:9:0"},{"expression":{"arguments":[{"id":724,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":715,"src":"6625:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":721,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6613:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":723,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6613:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":725,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6613:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":726,"nodeType":"ExpressionStatement","src":"6613:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"6549:5:0","parameters":{"id":716,"nodeType":"ParameterList","parameters":[{"constant":false,"id":715,"mutability":"mutable","name":"_v","nameLocation":"6571:2:0","nodeType":"VariableDeclaration","scope":728,"src":"6555:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":714,"name":"string","nodeType":"ElementaryTypeName","src":"6555:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6554:20:0"},"returnParameters":{"id":717,"nodeType":"ParameterList","parameters":[],"src":"6584:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":743,"nodeType":"FunctionDefinition","src":"6641:95:0","nodes":[],"body":{"id":742,"nodeType":"Block","src":"6685:51:0","nodes":[],"statements":[{"expression":{"id":734,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6695:9:0","subExpression":{"id":733,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6695:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":735,"nodeType":"ExpressionStatement","src":"6695:9:0"},{"expression":{"arguments":[{"id":739,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":730,"src":"6726:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":736,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6714:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":738,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6714:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":740,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6714:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":741,"nodeType":"ExpressionStatement","src":"6714:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"6650:5:0","parameters":{"id":731,"nodeType":"ParameterList","parameters":[{"constant":false,"id":730,"mutability":"mutable","name":"_v","nameLocation":"6672:2:0","nodeType":"VariableDeclaration","scope":743,"src":"6656:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":729,"name":"string","nodeType":"ElementaryTypeName","src":"6656:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6655:20:0"},"returnParameters":{"id":732,"nodeType":"ParameterList","parameters":[],"src":"6685:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":758,"nodeType":"FunctionDefinition","src":"6742:98:0","nodes":[],"body":{"id":757,"nodeType":"Block","src":"6788:52:0","nodes":[],"statements":[{"expression":{"id":749,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6798:9:0","subExpression":{"id":748,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6798:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":750,"nodeType":"ExpressionStatement","src":"6798:9:0"},{"expression":{"arguments":[{"id":754,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":745,"src":"6830:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":751,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6817:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":753,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":773,"src":"6817:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":755,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6817:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":756,"nodeType":"ExpressionStatement","src":"6817:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"6751:7:0","parameters":{"id":746,"nodeType":"ParameterList","parameters":[{"constant":false,"id":745,"mutability":"mutable","name":"_v","nameLocation":"6775:2:0","nodeType":"VariableDeclaration","scope":758,"src":"6759:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":744,"name":"string","nodeType":"ElementaryTypeName","src":"6759:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6758:20:0"},"returnParameters":{"id":747,"nodeType":"ParameterList","parameters":[],"src":"6788:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":773,"nodeType":"FunctionDefinition","src":"6846:97:0","nodes":[],"body":{"id":772,"nodeType":"Block","src":"6892:51:0","nodes":[],"statements":[{"expression":{"id":764,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6902:9:0","subExpression":{"id":763,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6902:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":765,"nodeType":"ExpressionStatement","src":"6902:9:0"},{"expression":{"arguments":[{"id":769,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":760,"src":"6933:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":766,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6921:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":768,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6921:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":770,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6921:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":771,"nodeType":"ExpressionStatement","src":"6921:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"6855:7:0","parameters":{"id":761,"nodeType":"ParameterList","parameters":[{"constant":false,"id":760,"mutability":"mutable","name":"_v","nameLocation":"6879:2:0","nodeType":"VariableDeclaration","scope":773,"src":"6863:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":759,"name":"string","nodeType":"ElementaryTypeName","src":"6863:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6862:20:0"},"returnParameters":{"id":762,"nodeType":"ParameterList","parameters":[],"src":"6892:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":785,"nodeType":"FunctionDefinition","src":"6949:84:0","nodes":[],"body":{"id":784,"nodeType":"Block","src":"7001:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":781,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":775,"src":"7023:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":778,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"7011:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":780,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"7011:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":782,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7011:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":783,"nodeType":"ExpressionStatement","src":"7011:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"6958:8:0","parameters":{"id":776,"nodeType":"ParameterList","parameters":[{"constant":false,"id":775,"mutability":"mutable","name":"_v","nameLocation":"6983:2:0","nodeType":"VariableDeclaration","scope":785,"src":"6967:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":774,"name":"string","nodeType":"ElementaryTypeName","src":"6967:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6966:20:0"},"returnParameters":{"id":777,"nodeType":"ParameterList","parameters":[],"src":"7001:0:0"},"scope":786,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[799],"contractKind":"contract","documentation":{"id":221,"nodeType":"StructuredDocumentation","src":"2997:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[786],"name":"ScriptExample","nameLocation":"3132:13:0","scope":969,"usedErrors":[]},{"id":799,"nodeType":"ContractDefinition","src":"7037:96:0","nodes":[{"id":788,"nodeType":"VariableDeclaration","src":"7059:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"7074:3:0","scope":799,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":787,"name":"uint256","nodeType":"ElementaryTypeName","src":"7059:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":798,"nodeType":"FunctionDefinition","src":"7084:47:0","nodes":[],"body":{"id":797,"nodeType":"Block","src":"7107:24:0","nodes":[],"statements":[{"expression":{"id":795,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":793,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":788,"src":"7117:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":794,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":790,"src":"7123:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"7117:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":796,"nodeType":"ExpressionStatement","src":"7117:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":791,"nodeType":"ParameterList","parameters":[{"constant":false,"id":790,"mutability":"mutable","name":"v","nameLocation":"7104:1:0","nodeType":"VariableDeclaration","scope":798,"src":"7096:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":789,"name":"uint256","nodeType":"ElementaryTypeName","src":"7096:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7095:11:0"},"returnParameters":{"id":792,"nodeType":"ParameterList","parameters":[],"src":"7107:0:0"},"scope":799,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[799],"name":"FooBar","nameLocation":"7046:6:0","scope":969,"usedErrors":[]},{"id":833,"nodeType":"ContractDefinition","src":"7135:281:0","nodes":[{"id":813,"nodeType":"VariableDeclaration","src":"7162:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7188:10:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":800,"name":"address","nodeType":"ElementaryTypeName","src":"7162:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":808,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7235:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":807,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7225:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":809,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7225:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":806,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7217:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":805,"name":"uint256","nodeType":"ElementaryTypeName","src":"7217:7:0","typeDescriptions":{}}},"id":810,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7217:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":804,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7209:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":803,"name":"uint160","nodeType":"ElementaryTypeName","src":"7209:7:0","typeDescriptions":{}}},"id":811,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7209:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":802,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":801,"name":"address","nodeType":"ElementaryTypeName","src":"7201:7:0","typeDescriptions":{}}},"id":812,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7201:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":819,"nodeType":"VariableDeclaration","src":"7262:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7283:2:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":815,"nodeType":"UserDefinedTypeName","pathNode":{"id":814,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7262:2:0"},"referencedDeclaration":83,"src":"7262:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":817,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":813,"src":"7291:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":816,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7288:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":818,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7288:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":832,"nodeType":"FunctionDefinition","src":"7309:105:0","nodes":[],"body":{"id":831,"nodeType":"Block","src":"7372:42:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":828,"name":"_addr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":821,"src":"7401:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":826,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":819,"src":"7389:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":827,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7389:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":829,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7389:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"functionReturnParameters":825,"id":830,"nodeType":"Return","src":"7382:25:0"}]},"functionSelector":"2d0335ab","implemented":true,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"7318:8:0","parameters":{"id":822,"nodeType":"ParameterList","parameters":[{"constant":false,"id":821,"mutability":"mutable","name":"_addr","nameLocation":"7335:5:0","nodeType":"VariableDeclaration","scope":832,"src":"7327:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":820,"name":"address","nodeType":"ElementaryTypeName","src":"7327:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7326:15:0"},"returnParameters":{"id":825,"nodeType":"ParameterList","parameters":[{"constant":false,"id":824,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":832,"src":"7363:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":823,"name":"uint256","nodeType":"ElementaryTypeName","src":"7363:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7362:9:0"},"scope":833,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"NonceGetter","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[833],"name":"NonceGetter","nameLocation":"7144:11:0","scope":969,"usedErrors":[]},{"id":852,"nodeType":"ContractDefinition","src":"7418:174:0","nodes":[{"id":835,"nodeType":"VariableDeclaration","src":"7448:18:0","nodes":[],"constant":false,"mutability":"mutable","name":"v","nameLocation":"7465:1:0","scope":852,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":834,"name":"uint256","nodeType":"ElementaryTypeName","src":"7448:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"id":843,"nodeType":"FunctionDefinition","src":"7473:36:0","nodes":[],"body":{"id":842,"nodeType":"Block","src":"7487:22:0","nodes":[],"statements":[{"expression":{"id":840,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":838,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7497:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"31","id":839,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7501:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"7497:5:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":841,"nodeType":"ExpressionStatement","src":"7497:5:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":836,"nodeType":"ParameterList","parameters":[],"src":"7484:2:0"},"returnParameters":{"id":837,"nodeType":"ParameterList","parameters":[],"src":"7487:0:0"},"scope":852,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":851,"nodeType":"FunctionDefinition","src":"7515:75:0","nodes":[],"body":{"id":850,"nodeType":"Block","src":"7565:25:0","nodes":[],"statements":[{"expression":{"id":848,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7582:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":847,"id":849,"nodeType":"Return","src":"7575:8:0"}]},"functionSelector":"20965255","implemented":true,"kind":"function","modifiers":[],"name":"getValue","nameLocation":"7524:8:0","parameters":{"id":844,"nodeType":"ParameterList","parameters":[],"src":"7532:2:0"},"returnParameters":{"id":847,"nodeType":"ParameterList","parameters":[{"constant":false,"id":846,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":851,"src":"7556:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":845,"name":"uint256","nodeType":"ElementaryTypeName","src":"7556:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7555:9:0"},"scope":852,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkedContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[852],"name":"ForkedContract","nameLocation":"7427:14:0","scope":969,"usedErrors":[]},{"id":968,"nodeType":"ContractDefinition","src":"7594:813:0","nodes":[{"id":866,"nodeType":"VariableDeclaration","src":"7620:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7646:10:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":853,"name":"address","nodeType":"ElementaryTypeName","src":"7620:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":861,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7693:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":860,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7683:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":862,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7683:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":859,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7675:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":858,"name":"uint256","nodeType":"ElementaryTypeName","src":"7675:7:0","typeDescriptions":{}}},"id":863,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7675:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":857,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7667:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":856,"name":"uint160","nodeType":"ElementaryTypeName","src":"7667:7:0","typeDescriptions":{}}},"id":864,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7667:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":855,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7659:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":854,"name":"address","nodeType":"ElementaryTypeName","src":"7659:7:0","typeDescriptions":{}}},"id":865,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7659:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":872,"nodeType":"VariableDeclaration","src":"7720:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7741:2:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":868,"nodeType":"UserDefinedTypeName","pathNode":{"id":867,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7720:2:0"},"referencedDeclaration":83,"src":"7720:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":870,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":866,"src":"7749:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":869,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7746:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":871,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7746:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":967,"nodeType":"FunctionDefinition","src":"7767:638:0","nodes":[],"body":{"id":966,"nodeType":"Block","src":"7791:614:0","nodes":[],"statements":[{"assignments":[876],"declarations":[{"constant":false,"id":876,"mutability":"mutable","name":"testAddr","nameLocation":"7809:8:0","nodeType":"VariableDeclaration","scope":966,"src":"7801:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":875,"name":"address","nodeType":"ElementaryTypeName","src":"7801:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":884,"initialValue":{"arguments":[{"arguments":[{"hexValue":"307831323334","id":881,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7836:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":880,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7828:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":879,"name":"uint160","nodeType":"ElementaryTypeName","src":"7828:7:0","typeDescriptions":{}}},"id":882,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7828:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":878,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7820:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":877,"name":"address","nodeType":"ElementaryTypeName","src":"7820:7:0","typeDescriptions":{}}},"id":883,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7820:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"7801:43:0"},{"assignments":[887],"declarations":[{"constant":false,"id":887,"mutability":"mutable","name":"fc","nameLocation":"7869:2:0","nodeType":"VariableDeclaration","scope":966,"src":"7854:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"},"typeName":{"id":886,"nodeType":"UserDefinedTypeName","pathNode":{"id":885,"name":"ForkedContract","nodeType":"IdentifierPath","referencedDeclaration":852,"src":"7854:14:0"},"referencedDeclaration":852,"src":"7854:14:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"visibility":"internal"}],"id":891,"initialValue":{"arguments":[{"id":889,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7889:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":888,"name":"ForkedContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":852,"src":"7874:14:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ForkedContract_$852_$","typeString":"type(contract ForkedContract)"}},"id":890,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7874:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"nodeType":"VariableDeclarationStatement","src":"7854:44:0"},{"expression":{"arguments":[{"hexValue":"666f726b31","id":895,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7929:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},"value":"fork1"},{"hexValue":"3132333435","id":896,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7938:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"}],"expression":{"id":892,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7909:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":894,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"7909:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":897,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7909:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":898,"nodeType":"ExpressionStatement","src":"7909:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":905,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":902,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7974:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":900,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7962:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":901,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7962:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":903,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7962:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3132333435","id":904,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7987:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"},"src":"7962:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":906,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7994:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":899,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"7954:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":907,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7954:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":908,"nodeType":"ExpressionStatement","src":"7954:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":914,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":910,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8036:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":911,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8036:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":912,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8036:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31","id":913,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8053:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"8036:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652031","id":915,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8056:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""},"value":"value should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""}],"id":909,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8028:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":916,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8028:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":917,"nodeType":"ExpressionStatement","src":"8028:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":925,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":919,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8094:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":920,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8094:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"31","id":923,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8122:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"}],"id":922,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8114:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":921,"name":"uint256","nodeType":"ElementaryTypeName","src":"8114:7:0","typeDescriptions":{}}},"id":924,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8114:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8094:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652031","id":926,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8126:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""},"value":"balance should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""}],"id":918,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8086:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":927,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8086:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":928,"nodeType":"ExpressionStatement","src":"8086:62:0"},{"expression":{"arguments":[{"hexValue":"666f726b32","id":932,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8179:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},"value":"fork2"},{"hexValue":"3233343536","id":933,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8188:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"}],"expression":{"id":929,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8159:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":931,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"8159:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":934,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8159:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":935,"nodeType":"ExpressionStatement","src":"8159:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":939,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8224:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":937,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8212:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":938,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"8212:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":940,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8212:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3233343536","id":941,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8237:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"},"src":"8212:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":943,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8244:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":936,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8204:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":944,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8204:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":945,"nodeType":"ExpressionStatement","src":"8204:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":951,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":947,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8286:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":948,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8286:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":949,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8286:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"32","id":950,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8303:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"src":"8286:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652032","id":952,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8306:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""},"value":"value should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""}],"id":946,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8278:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":953,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8278:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":954,"nodeType":"ExpressionStatement","src":"8278:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":962,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":956,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8344:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":957,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8344:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"32","id":960,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8372:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"}],"id":959,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8364:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":958,"name":"uint256","nodeType":"ElementaryTypeName","src":"8364:7:0","typeDescriptions":{}}},"id":961,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8364:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8344:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652032","id":963,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8376:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""},"value":"balance should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""}],"id":955,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8336:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":964,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8336:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":965,"nodeType":"ExpressionStatement","src":"8336:62:0"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"7776:3:0","parameters":{"id":873,"nodeType":"ParameterList","parameters":[],"src":"7779:2:0"},"returnParameters":{"id":874,"nodeType":"ParameterList","parameters":[],"src":"7791:0:0"},"scope":968,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkTester","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[968],"name":"ForkTester","nameLocation":"7603:10:0","scope":969,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/NonceGetter.json b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/NonceGetter.json new file mode 100644 index 0000000000000..5e6afe60d7101 --- /dev/null +++ b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/NonceGetter.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"getNonce","inputs":[{"name":"_addr","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b5061017e806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80632d0335ab14610030575b600080fd5b61004361003e36600461010a565b610055565b60405190815260200160405180910390f35b6040517f2d0335ab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152600090737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401602060405180830381865afa1580156100d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fa9190610147565b67ffffffffffffffff1692915050565b60006020828403121561011c57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461014057600080fd5b9392505050565b60006020828403121561015957600080fd5b815167ffffffffffffffff8116811461014057600080fdfea164736f6c634300080f000a","sourceMap":"7135:281:0:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c80632d0335ab14610030575b600080fd5b61004361003e36600461010a565b610055565b60405190815260200160405180910390f35b6040517f2d0335ab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152600090737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401602060405180830381865afa1580156100d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fa9190610147565b67ffffffffffffffff1692915050565b60006020828403121561011c57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461014057600080fd5b9392505050565b60006020828403121561015957600080fd5b815167ffffffffffffffff8116811461014057600080fdfea164736f6c634300080f000a","sourceMap":"7135:281:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7309:105;;;;;;:::i;:::-;;:::i;:::-;;;474:25:1;;;462:2;447:18;7309:105:0;;;;;;;;7389:18;;;;;686:42:1;674:55;;7389:18:0;;;656:74:1;7363:7:0;;7389:11;;;;629:18:1;;7389::0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;7382:25;;;7309:105;-1:-1:-1;;7309:105:0:o;14:309:1:-;73:6;126:2;114:9;105:7;101:23;97:32;94:52;;;142:1;139;132:12;94:52;181:9;168:23;231:42;224:5;220:54;213:5;210:65;200:93;;289:1;286;279:12;200:93;312:5;14:309;-1:-1:-1;;;14:309:1:o;741:288::-;810:6;863:2;851:9;842:7;838:23;834:32;831:52;;;879:1;876;869:12;831:52;911:9;905:16;961:18;954:5;950:30;943:5;940:41;930:69;;995:1;992;985:12","linkReferences":{}},"methodIdentifiers":{"getNonce(address)":"2d0335ab"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getNonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"NonceGetter\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28\",\"dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"stateMutability":"view","type":"function","name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"NonceGetter"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da","urls":["bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28","dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":969,"exportedSymbols":{"FooBar":[799],"ForkTester":[968],"ForkedContract":[852],"NonceGetter":[833],"ScriptExample":[786],"Vm":[83],"console":[220]},"nodeType":"SourceUnit","src":"32:8375:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":83,"nodeType":"ContractDefinition","src":"120:969:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":83,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":61,"nodeType":"FunctionDefinition","src":"739:108:0","nodes":[],"functionSelector":"3ebf73b4","implemented":false,"kind":"function","modifiers":[],"name":"getDeployedCode","nameLocation":"748:15:0","parameters":{"id":57,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56,"mutability":"mutable","name":"artifactPath","nameLocation":"780:12:0","nodeType":"VariableDeclaration","scope":61,"src":"764:28:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":55,"name":"string","nodeType":"ElementaryTypeName","src":"764:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"763:30:0"},"returnParameters":{"id":60,"nodeType":"ParameterList","parameters":[{"constant":false,"id":59,"mutability":"mutable","name":"runtimeBytecode","nameLocation":"830:15:0","nodeType":"VariableDeclaration","scope":61,"src":"817:28:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":58,"name":"bytes","nodeType":"ElementaryTypeName","src":"817:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"816:30:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":68,"nodeType":"FunctionDefinition","src":"852:74:0","nodes":[],"functionSelector":"b4d6c782","implemented":false,"kind":"function","modifiers":[],"name":"etch","nameLocation":"861:4:0","parameters":{"id":66,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"target","nameLocation":"874:6:0","nodeType":"VariableDeclaration","scope":68,"src":"866:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":62,"name":"address","nodeType":"ElementaryTypeName","src":"866:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":65,"mutability":"mutable","name":"newRuntimeBytecode","nameLocation":"897:18:0","nodeType":"VariableDeclaration","scope":68,"src":"882:33:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":64,"name":"bytes","nodeType":"ElementaryTypeName","src":"882:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"865:51:0"},"returnParameters":{"id":67,"nodeType":"ParameterList","parameters":[],"src":"925:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":73,"nodeType":"FunctionDefinition","src":"931:51:0","nodes":[],"functionSelector":"ea060291","implemented":false,"kind":"function","modifiers":[],"name":"allowCheatcodes","nameLocation":"940:15:0","parameters":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"account","nameLocation":"964:7:0","nodeType":"VariableDeclaration","scope":73,"src":"956:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":69,"name":"address","nodeType":"ElementaryTypeName","src":"956:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"955:17:0"},"returnParameters":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"981:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":82,"nodeType":"FunctionDefinition","src":"987:100:0","nodes":[],"functionSelector":"71ee464d","implemented":false,"kind":"function","modifiers":[],"name":"createSelectFork","nameLocation":"996:16:0","parameters":{"id":78,"nodeType":"ParameterList","parameters":[{"constant":false,"id":75,"mutability":"mutable","name":"forkName","nameLocation":"1029:8:0","nodeType":"VariableDeclaration","scope":82,"src":"1013:24:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":74,"name":"string","nodeType":"ElementaryTypeName","src":"1013:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":77,"mutability":"mutable","name":"blockNumber","nameLocation":"1047:11:0","nodeType":"VariableDeclaration","scope":82,"src":"1039:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":76,"name":"uint256","nodeType":"ElementaryTypeName","src":"1039:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1012:47:0"},"returnParameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":82,"src":"1078:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":79,"name":"uint256","nodeType":"ElementaryTypeName","src":"1078:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1077:9:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[83],"name":"Vm","nameLocation":"130:2:0","scope":969,"usedErrors":[]},{"id":220,"nodeType":"ContractDefinition","src":"1144:1851:0","nodes":[{"id":89,"nodeType":"VariableDeclaration","src":"1166:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"1183:15:0","scope":220,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":84,"name":"address","nodeType":"ElementaryTypeName","src":"1166:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":87,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1209:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":86,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":85,"name":"address","nodeType":"ElementaryTypeName","src":"1201:7:0","typeDescriptions":{}}},"id":88,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1201:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1259:235:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1432:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1451:37:0","statements":[{"nodeType":"YulAssignment","src":"1465:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1474:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1465:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":95,"isOffset":false,"isSlot":false,"src":"1474:4:0","valueSize":1},{"declaration":102,"isOffset":false,"isSlot":false,"src":"1465:5:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1442:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"1268:25:0","parameters":{"id":96,"nodeType":"ParameterList","parameters":[{"constant":false,"id":95,"mutability":"mutable","name":"fnIn","nameLocation":"1331:4:0","nodeType":"VariableDeclaration","scope":106,"src":"1294:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":94,"nodeType":"FunctionTypeName","parameterTypes":{"id":92,"nodeType":"ParameterList","parameters":[{"constant":false,"id":91,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":94,"src":"1303:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":90,"name":"bytes","nodeType":"ElementaryTypeName","src":"1303:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1302:14:0"},"returnParameterTypes":{"id":93,"nodeType":"ParameterList","parameters":[],"src":"1331:0:0"},"src":"1294:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"1293:43:0"},"returnParameters":{"id":103,"nodeType":"ParameterList","parameters":[{"constant":false,"id":102,"mutability":"mutable","name":"fnOut","nameLocation":"1421:5:0","nodeType":"VariableDeclaration","scope":106,"src":"1384:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":101,"nodeType":"FunctionTypeName","parameterTypes":{"id":99,"nodeType":"ParameterList","parameters":[{"constant":false,"id":98,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":101,"src":"1393:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":97,"name":"bytes","nodeType":"ElementaryTypeName","src":"1393:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1392:14:0"},"returnParameterTypes":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"1421:0:0"},"src":"1384:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1383:44:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":118,"nodeType":"FunctionDefinition","src":"1500:133:0","nodes":[],"body":{"id":117,"nodeType":"Block","src":"1561:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":114,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1618:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":112,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":134,"src":"1597:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":111,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1571:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":113,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":115,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":116,"nodeType":"ExpressionStatement","src":"1571:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1509:15:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"payload","nameLocation":"1538:7:0","nodeType":"VariableDeclaration","scope":118,"src":"1525:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":107,"name":"bytes","nodeType":"ElementaryTypeName","src":"1525:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1524:22:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1561:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":134,"nodeType":"FunctionDefinition","src":"1639:380:0","nodes":[],"body":{"id":133,"nodeType":"Block","src":"1703:316:0","nodes":[],"statements":[{"assignments":[124],"declarations":[{"constant":false,"id":124,"mutability":"mutable","name":"payloadLength","nameLocation":"1721:13:0","nodeType":"VariableDeclaration","scope":133,"src":"1713:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":123,"name":"uint256","nodeType":"ElementaryTypeName","src":"1713:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":127,"initialValue":{"expression":{"id":125,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":120,"src":"1737:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":126,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1737:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1713:38:0"},{"assignments":[129],"declarations":[{"constant":false,"id":129,"mutability":"mutable","name":"consoleAddress","nameLocation":"1769:14:0","nodeType":"VariableDeclaration","scope":133,"src":"1761:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":128,"name":"address","nodeType":"ElementaryTypeName","src":"1761:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":131,"initialValue":{"id":130,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":89,"src":"1786:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1761:40:0"},{"AST":{"nodeType":"YulBlock","src":"1863:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1877:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1901:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1910:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1897:3:0"},"nodeType":"YulFunctionCall","src":"1897:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1881:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1926:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1946:3:0"},"nodeType":"YulFunctionCall","src":"1946:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1953:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1969:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1983:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1998:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"2001:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1935:10:0"},"nodeType":"YulFunctionCall","src":"1935:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1930:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":129,"isOffset":false,"isSlot":false,"src":"1953:14:0","valueSize":1},{"declaration":120,"isOffset":false,"isSlot":false,"src":"1901:7:0","valueSize":1},{"declaration":124,"isOffset":false,"isSlot":false,"src":"1983:13:0","valueSize":1}],"id":132,"nodeType":"InlineAssembly","src":"1854:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1648:19:0","parameters":{"id":121,"nodeType":"ParameterList","parameters":[{"constant":false,"id":120,"mutability":"mutable","name":"payload","nameLocation":"1681:7:0","nodeType":"VariableDeclaration","scope":134,"src":"1668:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":119,"name":"bytes","nodeType":"ElementaryTypeName","src":"1668:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1667:22:0"},"returnParameters":{"id":122,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":220,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":148,"nodeType":"FunctionDefinition","src":"2025:164:0","nodes":[],"body":{"id":147,"nodeType":"Block","src":"2070:119:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":142,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2120:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":143,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":136,"src":"2135:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":140,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2096:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":141,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2096: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":144,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2096:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":139,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2080:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":145,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2080:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":146,"nodeType":"ExpressionStatement","src":"2080:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2034:3:0","parameters":{"id":137,"nodeType":"ParameterList","parameters":[{"constant":false,"id":136,"mutability":"mutable","name":"p0","nameLocation":"2052:2:0","nodeType":"VariableDeclaration","scope":148,"src":"2038:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":135,"name":"string","nodeType":"ElementaryTypeName","src":"2038:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2037:18:0"},"returnParameters":{"id":138,"nodeType":"ParameterList","parameters":[],"src":"2070:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":165,"nodeType":"FunctionDefinition","src":"2195:182:0","nodes":[],"body":{"id":164,"nodeType":"Block","src":"2249:128:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":158,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2299:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":159,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":150,"src":"2319:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":160,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":152,"src":"2323:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":156,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2275:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":157,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2275: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":161,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2275:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":155,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2259:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":162,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2259:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":163,"nodeType":"ExpressionStatement","src":"2259:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2204:3:0","parameters":{"id":153,"nodeType":"ParameterList","parameters":[{"constant":false,"id":150,"mutability":"mutable","name":"p0","nameLocation":"2222:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2208:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":149,"name":"string","nodeType":"ElementaryTypeName","src":"2208:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":152,"mutability":"mutable","name":"p1","nameLocation":"2231:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2226:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":151,"name":"bool","nodeType":"ElementaryTypeName","src":"2226:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"2207:27:0"},"returnParameters":{"id":154,"nodeType":"ParameterList","parameters":[],"src":"2249:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":182,"nodeType":"FunctionDefinition","src":"2383:188:0","nodes":[],"body":{"id":181,"nodeType":"Block","src":"2440:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":175,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2490:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":176,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":167,"src":"2513:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":177,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":169,"src":"2517:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":173,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2466:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":174,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2466: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":178,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2466:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":172,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2450:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":179,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2450:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":180,"nodeType":"ExpressionStatement","src":"2450:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2392:3:0","parameters":{"id":170,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"mutability":"mutable","name":"p0","nameLocation":"2410:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2396:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":166,"name":"string","nodeType":"ElementaryTypeName","src":"2396:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":169,"mutability":"mutable","name":"p1","nameLocation":"2422:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2414:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":168,"name":"uint256","nodeType":"ElementaryTypeName","src":"2414:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2395:30:0"},"returnParameters":{"id":171,"nodeType":"ParameterList","parameters":[],"src":"2440:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":199,"nodeType":"FunctionDefinition","src":"2577:188:0","nodes":[],"body":{"id":198,"nodeType":"Block","src":"2634:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":192,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2684:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":193,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":184,"src":"2707:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":194,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"2711:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":190,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2660:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":191,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2660: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":195,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2660:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":189,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2644:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":196,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2644:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":197,"nodeType":"ExpressionStatement","src":"2644:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2586:3:0","parameters":{"id":187,"nodeType":"ParameterList","parameters":[{"constant":false,"id":184,"mutability":"mutable","name":"p0","nameLocation":"2604:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2590:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":183,"name":"string","nodeType":"ElementaryTypeName","src":"2590:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":186,"mutability":"mutable","name":"p1","nameLocation":"2616:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2608:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":185,"name":"address","nodeType":"ElementaryTypeName","src":"2608:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2589:30:0"},"returnParameters":{"id":188,"nodeType":"ParameterList","parameters":[],"src":"2634:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":219,"nodeType":"FunctionDefinition","src":"2771:222:0","nodes":[],"body":{"id":218,"nodeType":"Block","src":"2852:141:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":211,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2902:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":212,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":201,"src":"2931:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":213,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":203,"src":"2935:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":214,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":205,"src":"2939:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":209,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2878:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":210,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2878: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":215,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2878:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":208,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2862:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":216,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2862:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":217,"nodeType":"ExpressionStatement","src":"2862:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2780:3:0","parameters":{"id":206,"nodeType":"ParameterList","parameters":[{"constant":false,"id":201,"mutability":"mutable","name":"p0","nameLocation":"2798:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2784:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":200,"name":"string","nodeType":"ElementaryTypeName","src":"2784:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":203,"mutability":"mutable","name":"p1","nameLocation":"2816:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2802:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":202,"name":"string","nodeType":"ElementaryTypeName","src":"2802:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":205,"mutability":"mutable","name":"p2","nameLocation":"2834:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2820:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":204,"name":"string","nodeType":"ElementaryTypeName","src":"2820:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2783:54:0"},"returnParameters":{"id":207,"nodeType":"ParameterList","parameters":[],"src":"2852:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[220],"name":"console","nameLocation":"1152:7:0","scope":969,"usedErrors":[]},{"id":786,"nodeType":"ContractDefinition","src":"3123:3912:0","nodes":[{"id":235,"nodeType":"VariableDeclaration","src":"3152:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"3178:10:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":222,"name":"address","nodeType":"ElementaryTypeName","src":"3152:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3225:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":229,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"3215:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":231,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3215:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":228,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3207:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":227,"name":"uint256","nodeType":"ElementaryTypeName","src":"3207:7:0","typeDescriptions":{}}},"id":232,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3207:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":226,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3199:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":225,"name":"uint160","nodeType":"ElementaryTypeName","src":"3199:7:0","typeDescriptions":{}}},"id":233,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3199:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":224,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3191:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":223,"name":"address","nodeType":"ElementaryTypeName","src":"3191:7:0","typeDescriptions":{}}},"id":234,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3191:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":241,"nodeType":"VariableDeclaration","src":"3252:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"3273:2:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":237,"nodeType":"UserDefinedTypeName","pathNode":{"id":236,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"3252:2:0"},"referencedDeclaration":83,"src":"3252:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":239,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":235,"src":"3281:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":238,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"3278:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":240,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3278:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":243,"nodeType":"VariableDeclaration","src":"3356:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"3371:7:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":242,"name":"uint256","nodeType":"ElementaryTypeName","src":"3356:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":456,"nodeType":"FunctionDefinition","src":"3468:1428:0","nodes":[],"body":{"id":455,"nodeType":"Block","src":"3490:1406:0","nodes":[],"statements":[{"assignments":[248],"declarations":[{"constant":false,"id":248,"mutability":"mutable","name":"x","nameLocation":"3505:1:0","nodeType":"VariableDeclaration","scope":455,"src":"3500:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":247,"name":"bool","nodeType":"ElementaryTypeName","src":"3500:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":254,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":251,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3518:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":252,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"3534:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":249,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3509:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":250,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"3509:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"3500:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":258,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3562:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":259,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":248,"src":"3585:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":255,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3550:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":257,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":165,"src":"3550:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":260,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3550:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":261,"nodeType":"ExpressionStatement","src":"3550:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":265,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3610:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":268,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3635:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":267,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3627:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":266,"name":"address","nodeType":"ElementaryTypeName","src":"3627:7:0","typeDescriptions":{}}},"id":269,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3627:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":262,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3598:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3598:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3598:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":271,"nodeType":"ExpressionStatement","src":"3598:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":275,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3663:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":280,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3701:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":279,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3693:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":278,"name":"address","nodeType":"ElementaryTypeName","src":"3693:7:0","typeDescriptions":{}}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3693:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":276,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3681:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3681:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":282,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":272,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3651:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":274,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3651:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":283,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3651:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":284,"nodeType":"ExpressionStatement","src":"3651:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":288,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3730:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":291,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3753:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3753:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":290,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3745:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":289,"name":"address","nodeType":"ElementaryTypeName","src":"3745:7:0","typeDescriptions":{}}},"id":293,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3745:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":285,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3718:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3718:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3718:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":295,"nodeType":"ExpressionStatement","src":"3718:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":299,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3787:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":304,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3823:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":305,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3823:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":303,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3815:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":302,"name":"address","nodeType":"ElementaryTypeName","src":"3815:7:0","typeDescriptions":{}}},"id":306,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3815:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":300,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3803:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3803:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3803:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":296,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3775:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":298,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3775:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":308,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3775:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":309,"nodeType":"ExpressionStatement","src":"3775:61:0"},{"assignments":[311],"declarations":[{"constant":false,"id":311,"mutability":"mutable","name":"json","nameLocation":"3861:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3847:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":310,"name":"string","nodeType":"ElementaryTypeName","src":"3847:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":313,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3868:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3847:55:0"},{"assignments":[318],"declarations":[{"constant":false,"id":318,"mutability":"mutable","name":"keys","nameLocation":"3928:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3912:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":316,"name":"string","nodeType":"ElementaryTypeName","src":"3912:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":317,"nodeType":"ArrayTypeName","src":"3912:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":324,"initialValue":{"arguments":[{"id":321,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":311,"src":"3952:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3958:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":319,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3935:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":320,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3935:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":323,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3935:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3912:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":328,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3995:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":329,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4003:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":331,"indexExpression":{"hexValue":"30","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4008:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4003:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":332,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4012:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":334,"indexExpression":{"hexValue":"31","id":333,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4017:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4012:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":325,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3983:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":327,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":219,"src":"3983:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3983:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":336,"nodeType":"ExpressionStatement","src":"3983:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":340,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4042:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":337,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4031:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":339,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4031:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4031:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":342,"nodeType":"ExpressionStatement","src":"4031:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":350,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4098:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4090:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":348,"name":"uint160","nodeType":"ElementaryTypeName","src":"4090:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4090:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":347,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4082:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":346,"name":"address","nodeType":"ElementaryTypeName","src":"4082:7:0","typeDescriptions":{}}},"id":352,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4082:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":343,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4068:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":345,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"4068:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":353,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4068:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":354,"nodeType":"ExpressionStatement","src":"4068:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":358,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4126:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":355,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4115:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":357,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4115:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":359,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4115:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":360,"nodeType":"ExpressionStatement","src":"4115:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":364,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4163:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":367,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4198:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":368,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"4198:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":366,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4190:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":365,"name":"address","nodeType":"ElementaryTypeName","src":"4190:7:0","typeDescriptions":{}}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4190:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":361,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4151:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4151:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":370,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4151:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":371,"nodeType":"ExpressionStatement","src":"4151:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":375,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4232:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":378,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4270:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":377,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4262:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":376,"name":"address","nodeType":"ElementaryTypeName","src":"4262:7:0","typeDescriptions":{}}},"id":379,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4262:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":372,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4220:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":374,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4220:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":380,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4220:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":381,"nodeType":"ExpressionStatement","src":"4220:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4297:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":382,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4286:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4286:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":386,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4286:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":387,"nodeType":"ExpressionStatement","src":"4286:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4322:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":390,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"4322:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":391,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4322:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":392,"nodeType":"ExpressionStatement","src":"4322:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":396,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4357:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":393,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4346:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4346:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":397,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4346:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":398,"nodeType":"ExpressionStatement","src":"4346:33:0"},{"assignments":[400],"declarations":[{"constant":false,"id":400,"mutability":"mutable","name":"tmpNonceGetter","nameLocation":"4480:14:0","nodeType":"VariableDeclaration","scope":455,"src":"4472:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":399,"name":"address","nodeType":"ElementaryTypeName","src":"4472:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":413,"initialValue":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"74656d70206e6f6e6365207465737420676574746572","id":408,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4531:24:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""},"value":"temp nonce test getter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""}],"id":407,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"4521:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":409,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4521:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":406,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4513:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":405,"name":"uint256","nodeType":"ElementaryTypeName","src":"4513:7:0","typeDescriptions":{}}},"id":410,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4513:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":404,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4505:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":403,"name":"uint160","nodeType":"ElementaryTypeName","src":"4505:7:0","typeDescriptions":{}}},"id":411,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4505:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":402,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4497:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":401,"name":"address","nodeType":"ElementaryTypeName","src":"4497:7:0","typeDescriptions":{}}},"id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4497:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"4472:87:0"},{"expression":{"arguments":[{"id":417,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4577:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"hexValue":"5363726970744578616d706c652e732e736f6c3a4e6f6e6365476574746572","id":420,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4612:33:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""},"value":"ScriptExample.s.sol:NonceGetter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""}],"expression":{"id":418,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4593:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getDeployedCode","nodeType":"MemberAccess","referencedDeclaration":61,"src":"4593:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) view external returns (bytes memory)"}},"id":421,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4593:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":414,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4569:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":416,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"etch","nodeType":"MemberAccess","referencedDeclaration":68,"src":"4569:7:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory) external"}},"id":422,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4569:78:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":423,"nodeType":"ExpressionStatement","src":"4569:78:0"},{"expression":{"arguments":[{"id":427,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4676:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":424,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4657:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":426,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"allowCheatcodes","nodeType":"MemberAccess","referencedDeclaration":73,"src":"4657:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":428,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":429,"nodeType":"ExpressionStatement","src":"4657:34:0"},{"assignments":[431],"declarations":[{"constant":false,"id":431,"mutability":"mutable","name":"v","nameLocation":"4709:1:0","nodeType":"VariableDeclaration","scope":455,"src":"4701:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":430,"name":"uint256","nodeType":"ElementaryTypeName","src":"4701:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":441,"initialValue":{"arguments":[{"arguments":[{"id":438,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4758:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":437,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4750:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":436,"name":"address","nodeType":"ElementaryTypeName","src":"4750:7:0","typeDescriptions":{}}},"id":439,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4750:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"arguments":[{"id":433,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4725:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":432,"name":"NonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":833,"src":"4713:11:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_NonceGetter_$833_$","typeString":"type(contract NonceGetter)"}},"id":434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_NonceGetter_$833","typeString":"contract NonceGetter"}},"id":435,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":832,"src":"4713:36:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view external returns (uint256)"}},"id":440,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"4701:63:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683a","id":445,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4786:68:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},"value":"nonce from nonce getter, no explicit access required with vm.etch:"},{"id":446,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":431,"src":"4856:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":442,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4774:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":444,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"4774:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4774:84:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":448,"nodeType":"ExpressionStatement","src":"4774:84:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":452,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4881:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":449,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4869:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":451,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"4869:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4869:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":454,"nodeType":"ExpressionStatement","src":"4869:20:0"}]},"documentation":{"id":244,"nodeType":"StructuredDocumentation","src":"3385:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"3477:3:0","parameters":{"id":245,"nodeType":"ParameterList","parameters":[],"src":"3480:2:0"},"returnParameters":{"id":246,"nodeType":"ParameterList","parameters":[],"src":"3490:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":689,"nodeType":"FunctionDefinition","src":"4963:1333:0","nodes":[],"body":{"id":688,"nodeType":"Block","src":"4994:1302:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":463,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5016:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":470,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5059:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":469,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5051:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":468,"name":"address","nodeType":"ElementaryTypeName","src":"5051:7:0","typeDescriptions":{}}},"id":471,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5051:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":466,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5039:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":467,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5039:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5039:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":465,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5031:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":464,"name":"uint256","nodeType":"ElementaryTypeName","src":"5031:7:0","typeDescriptions":{}}},"id":473,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5031:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":460,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5004:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":462,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"5004:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":474,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5004:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":475,"nodeType":"ExpressionStatement","src":"5004:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":479,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5090:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":476,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5078:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":478,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5078:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":480,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5078:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":481,"nodeType":"ExpressionStatement","src":"5078:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":482,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5117:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5117:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":485,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5117:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":486,"nodeType":"ExpressionStatement","src":"5117:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":490,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5152:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":487,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5141:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":489,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5141:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":491,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5141:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":492,"nodeType":"ExpressionStatement","src":"5141:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":496,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5188:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":493,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5177:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5177:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":497,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5177:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":498,"nodeType":"ExpressionStatement","src":"5177:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":502,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5226:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":499,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5214:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5214:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":503,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5214:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":504,"nodeType":"ExpressionStatement","src":"5214:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5291:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":511,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5283:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":510,"name":"uint160","nodeType":"ElementaryTypeName","src":"5283:7:0","typeDescriptions":{}}},"id":513,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5283:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5275:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":508,"name":"address","nodeType":"ElementaryTypeName","src":"5275:7:0","typeDescriptions":{}}},"id":514,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5275:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":505,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5257:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":507,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5257:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":515,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5257:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":516,"nodeType":"ExpressionStatement","src":"5257:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":520,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5323:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":517,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5312:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":519,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5312:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":521,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5312:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":522,"nodeType":"ExpressionStatement","src":"5312:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":526,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5362:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":523,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5351:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":525,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5351:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5351:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":528,"nodeType":"ExpressionStatement","src":"5351:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":532,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5404:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":529,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5390:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":531,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":785,"src":"5390:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":533,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5390:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":534,"nodeType":"ExpressionStatement","src":"5390:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":535,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5431:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":537,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5431:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":538,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5431:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":539,"nodeType":"ExpressionStatement","src":"5431:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":543,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5470:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":540,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5459:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":542,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5459:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5459:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":545,"nodeType":"ExpressionStatement","src":"5459:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":549,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5511:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":546,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5499:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5499:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":550,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5499:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":551,"nodeType":"ExpressionStatement","src":"5499:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":559,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5572:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":558,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5564:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":557,"name":"uint160","nodeType":"ElementaryTypeName","src":"5564:7:0","typeDescriptions":{}}},"id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5564:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":556,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5556:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":555,"name":"address","nodeType":"ElementaryTypeName","src":"5556:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5556:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":552,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5538:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":554,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5538:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5538:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":563,"nodeType":"ExpressionStatement","src":"5538:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":567,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5604:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":564,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5591:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":758,"src":"5591:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":568,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5591:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":569,"nodeType":"ExpressionStatement","src":"5591:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":570,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5623:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5623:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5623:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"5623:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5664:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5652:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5652:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5652:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"5652:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":588,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5725:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":587,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5717:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":586,"name":"uint160","nodeType":"ElementaryTypeName","src":"5717:7:0","typeDescriptions":{}}},"id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5717:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":585,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5709:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":584,"name":"address","nodeType":"ElementaryTypeName","src":"5709:7:0","typeDescriptions":{}}},"id":590,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5709:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5696:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5696:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":591,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5696:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":592,"nodeType":"ExpressionStatement","src":"5696:40:0"},{"assignments":[595],"declarations":[{"constant":false,"id":595,"mutability":"mutable","name":"x","nameLocation":"5753:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5746:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":594,"nodeType":"UserDefinedTypeName","pathNode":{"id":593,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5746:6:0"},"referencedDeclaration":799,"src":"5746:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":601,"initialValue":{"arguments":[{"hexValue":"31323334","id":599,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5768:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":598,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5757:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":597,"nodeType":"UserDefinedTypeName","pathNode":{"id":596,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5761:6:0"},"referencedDeclaration":799,"src":"5761:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":600,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5757:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5746:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":607,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":603,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":595,"src":"5791:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"5791:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5791:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":606,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5802:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"5791:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323334","id":608,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5808:35:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""},"value":"FooBar: foo in create is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""}],"id":602,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"5783:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":609,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5783:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":610,"nodeType":"ExpressionStatement","src":"5783:61:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":614,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5867:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":611,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5855:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":613,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5855:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":615,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5855:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":616,"nodeType":"ExpressionStatement","src":"5855:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5917:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":623,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5909:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":622,"name":"uint160","nodeType":"ElementaryTypeName","src":"5909:7:0","typeDescriptions":{}}},"id":625,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5909:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":621,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5901:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":620,"name":"address","nodeType":"ElementaryTypeName","src":"5901:7:0","typeDescriptions":{}}},"id":626,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5901:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":617,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5888:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":627,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5888:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":628,"nodeType":"ExpressionStatement","src":"5888:38:0"},{"assignments":[631],"declarations":[{"constant":false,"id":631,"mutability":"mutable","name":"y","nameLocation":"5943:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5936:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":630,"nodeType":"UserDefinedTypeName","pathNode":{"id":629,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5936:6:0"},"referencedDeclaration":799,"src":"5936:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":645,"initialValue":{"arguments":[{"hexValue":"31323334","id":643,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5986:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":634,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5947:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":633,"nodeType":"UserDefinedTypeName","pathNode":{"id":632,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5951:6:0"},"referencedDeclaration":799,"src":"5951:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":642,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":639,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5980:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":638,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5972:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":637,"name":"uint256","nodeType":"ElementaryTypeName","src":"5972:7:0","typeDescriptions":{}}},"id":640,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5972:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":636,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5964:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":635,"name":"bytes32","nodeType":"ElementaryTypeName","src":"5964:7:0","typeDescriptions":{}}},"id":641,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5964:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"5947:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":644,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5947:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5936:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":651,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":647,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":631,"src":"6009:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":648,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"6009:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":649,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6009:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":650,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6020:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"6009:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e2063726561746532206973206e6f742031323334","id":652,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6026:36:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""},"value":"FooBar: foo in create2 is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""}],"id":646,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"6001:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6001:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":654,"nodeType":"ExpressionStatement","src":"6001:62:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":658,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6085:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":655,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6073:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":657,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6073:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":659,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6073:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":660,"nodeType":"ExpressionStatement","src":"6073:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":661,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6177:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"6177:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":664,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6177:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":665,"nodeType":"ExpressionStatement","src":"6177:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":669,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6212:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":668,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"6201:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":667,"nodeType":"UserDefinedTypeName","pathNode":{"id":666,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"6205:6:0"},"referencedDeclaration":799,"src":"6205:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":670,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6201:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":671,"nodeType":"ExpressionStatement","src":"6201:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":675,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6240:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":682,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6281:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":681,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6273:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":680,"name":"address","nodeType":"ElementaryTypeName","src":"6273:7:0","typeDescriptions":{}}},"id":683,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6273:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":678,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6261:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"6261:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":684,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6261:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":677,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6253:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":676,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:0","typeDescriptions":{}}},"id":685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6253:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":672,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6228:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":674,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"6228:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":686,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6228:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":687,"nodeType":"ExpressionStatement","src":"6228:61:0"}]},"documentation":{"id":457,"nodeType":"StructuredDocumentation","src":"4902:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"4972:12:0","parameters":{"id":458,"nodeType":"ParameterList","parameters":[],"src":"4984:2:0"},"returnParameters":{"id":459,"nodeType":"ParameterList","parameters":[],"src":"4994:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":713,"nodeType":"FunctionDefinition","src":"6391:143:0","nodes":[],"body":{"id":712,"nodeType":"Block","src":"6440:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":698,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":692,"src":"6462:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":695,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6450:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":697,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6450:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":699,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6450:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":700,"nodeType":"ExpressionStatement","src":"6450:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6487:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":707,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"6515:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":708,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"6515:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":706,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6507:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":705,"name":"address","nodeType":"ElementaryTypeName","src":"6507:7:0","typeDescriptions":{}}},"id":709,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6507:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":701,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6475:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"6475:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":710,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6475:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":711,"nodeType":"ExpressionStatement","src":"6475:52:0"}]},"documentation":{"id":690,"nodeType":"StructuredDocumentation","src":"6302:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"6400:5:0","parameters":{"id":693,"nodeType":"ParameterList","parameters":[{"constant":false,"id":692,"mutability":"mutable","name":"_v","nameLocation":"6422:2:0","nodeType":"VariableDeclaration","scope":713,"src":"6406:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":691,"name":"string","nodeType":"ElementaryTypeName","src":"6406:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6405:20:0"},"returnParameters":{"id":694,"nodeType":"ParameterList","parameters":[],"src":"6440:0:0"},"scope":786,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":728,"nodeType":"FunctionDefinition","src":"6540:95:0","nodes":[],"body":{"id":727,"nodeType":"Block","src":"6584:51:0","nodes":[],"statements":[{"expression":{"id":719,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6594:9:0","subExpression":{"id":718,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6594:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":720,"nodeType":"ExpressionStatement","src":"6594:9:0"},{"expression":{"arguments":[{"id":724,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":715,"src":"6625:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":721,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6613:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":723,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6613:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":725,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6613:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":726,"nodeType":"ExpressionStatement","src":"6613:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"6549:5:0","parameters":{"id":716,"nodeType":"ParameterList","parameters":[{"constant":false,"id":715,"mutability":"mutable","name":"_v","nameLocation":"6571:2:0","nodeType":"VariableDeclaration","scope":728,"src":"6555:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":714,"name":"string","nodeType":"ElementaryTypeName","src":"6555:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6554:20:0"},"returnParameters":{"id":717,"nodeType":"ParameterList","parameters":[],"src":"6584:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":743,"nodeType":"FunctionDefinition","src":"6641:95:0","nodes":[],"body":{"id":742,"nodeType":"Block","src":"6685:51:0","nodes":[],"statements":[{"expression":{"id":734,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6695:9:0","subExpression":{"id":733,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6695:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":735,"nodeType":"ExpressionStatement","src":"6695:9:0"},{"expression":{"arguments":[{"id":739,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":730,"src":"6726:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":736,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6714:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":738,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6714:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":740,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6714:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":741,"nodeType":"ExpressionStatement","src":"6714:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"6650:5:0","parameters":{"id":731,"nodeType":"ParameterList","parameters":[{"constant":false,"id":730,"mutability":"mutable","name":"_v","nameLocation":"6672:2:0","nodeType":"VariableDeclaration","scope":743,"src":"6656:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":729,"name":"string","nodeType":"ElementaryTypeName","src":"6656:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6655:20:0"},"returnParameters":{"id":732,"nodeType":"ParameterList","parameters":[],"src":"6685:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":758,"nodeType":"FunctionDefinition","src":"6742:98:0","nodes":[],"body":{"id":757,"nodeType":"Block","src":"6788:52:0","nodes":[],"statements":[{"expression":{"id":749,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6798:9:0","subExpression":{"id":748,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6798:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":750,"nodeType":"ExpressionStatement","src":"6798:9:0"},{"expression":{"arguments":[{"id":754,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":745,"src":"6830:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":751,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6817:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":753,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":773,"src":"6817:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":755,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6817:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":756,"nodeType":"ExpressionStatement","src":"6817:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"6751:7:0","parameters":{"id":746,"nodeType":"ParameterList","parameters":[{"constant":false,"id":745,"mutability":"mutable","name":"_v","nameLocation":"6775:2:0","nodeType":"VariableDeclaration","scope":758,"src":"6759:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":744,"name":"string","nodeType":"ElementaryTypeName","src":"6759:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6758:20:0"},"returnParameters":{"id":747,"nodeType":"ParameterList","parameters":[],"src":"6788:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":773,"nodeType":"FunctionDefinition","src":"6846:97:0","nodes":[],"body":{"id":772,"nodeType":"Block","src":"6892:51:0","nodes":[],"statements":[{"expression":{"id":764,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6902:9:0","subExpression":{"id":763,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6902:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":765,"nodeType":"ExpressionStatement","src":"6902:9:0"},{"expression":{"arguments":[{"id":769,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":760,"src":"6933:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":766,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6921:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":768,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6921:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":770,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6921:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":771,"nodeType":"ExpressionStatement","src":"6921:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"6855:7:0","parameters":{"id":761,"nodeType":"ParameterList","parameters":[{"constant":false,"id":760,"mutability":"mutable","name":"_v","nameLocation":"6879:2:0","nodeType":"VariableDeclaration","scope":773,"src":"6863:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":759,"name":"string","nodeType":"ElementaryTypeName","src":"6863:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6862:20:0"},"returnParameters":{"id":762,"nodeType":"ParameterList","parameters":[],"src":"6892:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":785,"nodeType":"FunctionDefinition","src":"6949:84:0","nodes":[],"body":{"id":784,"nodeType":"Block","src":"7001:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":781,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":775,"src":"7023:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":778,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"7011:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":780,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"7011:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":782,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7011:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":783,"nodeType":"ExpressionStatement","src":"7011:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"6958:8:0","parameters":{"id":776,"nodeType":"ParameterList","parameters":[{"constant":false,"id":775,"mutability":"mutable","name":"_v","nameLocation":"6983:2:0","nodeType":"VariableDeclaration","scope":785,"src":"6967:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":774,"name":"string","nodeType":"ElementaryTypeName","src":"6967:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6966:20:0"},"returnParameters":{"id":777,"nodeType":"ParameterList","parameters":[],"src":"7001:0:0"},"scope":786,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[799],"contractKind":"contract","documentation":{"id":221,"nodeType":"StructuredDocumentation","src":"2997:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[786],"name":"ScriptExample","nameLocation":"3132:13:0","scope":969,"usedErrors":[]},{"id":799,"nodeType":"ContractDefinition","src":"7037:96:0","nodes":[{"id":788,"nodeType":"VariableDeclaration","src":"7059:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"7074:3:0","scope":799,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":787,"name":"uint256","nodeType":"ElementaryTypeName","src":"7059:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":798,"nodeType":"FunctionDefinition","src":"7084:47:0","nodes":[],"body":{"id":797,"nodeType":"Block","src":"7107:24:0","nodes":[],"statements":[{"expression":{"id":795,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":793,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":788,"src":"7117:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":794,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":790,"src":"7123:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"7117:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":796,"nodeType":"ExpressionStatement","src":"7117:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":791,"nodeType":"ParameterList","parameters":[{"constant":false,"id":790,"mutability":"mutable","name":"v","nameLocation":"7104:1:0","nodeType":"VariableDeclaration","scope":798,"src":"7096:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":789,"name":"uint256","nodeType":"ElementaryTypeName","src":"7096:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7095:11:0"},"returnParameters":{"id":792,"nodeType":"ParameterList","parameters":[],"src":"7107:0:0"},"scope":799,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[799],"name":"FooBar","nameLocation":"7046:6:0","scope":969,"usedErrors":[]},{"id":833,"nodeType":"ContractDefinition","src":"7135:281:0","nodes":[{"id":813,"nodeType":"VariableDeclaration","src":"7162:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7188:10:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":800,"name":"address","nodeType":"ElementaryTypeName","src":"7162:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":808,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7235:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":807,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7225:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":809,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7225:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":806,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7217:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":805,"name":"uint256","nodeType":"ElementaryTypeName","src":"7217:7:0","typeDescriptions":{}}},"id":810,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7217:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":804,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7209:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":803,"name":"uint160","nodeType":"ElementaryTypeName","src":"7209:7:0","typeDescriptions":{}}},"id":811,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7209:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":802,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":801,"name":"address","nodeType":"ElementaryTypeName","src":"7201:7:0","typeDescriptions":{}}},"id":812,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7201:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":819,"nodeType":"VariableDeclaration","src":"7262:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7283:2:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":815,"nodeType":"UserDefinedTypeName","pathNode":{"id":814,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7262:2:0"},"referencedDeclaration":83,"src":"7262:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":817,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":813,"src":"7291:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":816,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7288:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":818,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7288:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":832,"nodeType":"FunctionDefinition","src":"7309:105:0","nodes":[],"body":{"id":831,"nodeType":"Block","src":"7372:42:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":828,"name":"_addr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":821,"src":"7401:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":826,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":819,"src":"7389:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":827,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7389:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":829,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7389:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"functionReturnParameters":825,"id":830,"nodeType":"Return","src":"7382:25:0"}]},"functionSelector":"2d0335ab","implemented":true,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"7318:8:0","parameters":{"id":822,"nodeType":"ParameterList","parameters":[{"constant":false,"id":821,"mutability":"mutable","name":"_addr","nameLocation":"7335:5:0","nodeType":"VariableDeclaration","scope":832,"src":"7327:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":820,"name":"address","nodeType":"ElementaryTypeName","src":"7327:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7326:15:0"},"returnParameters":{"id":825,"nodeType":"ParameterList","parameters":[{"constant":false,"id":824,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":832,"src":"7363:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":823,"name":"uint256","nodeType":"ElementaryTypeName","src":"7363:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7362:9:0"},"scope":833,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"NonceGetter","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[833],"name":"NonceGetter","nameLocation":"7144:11:0","scope":969,"usedErrors":[]},{"id":852,"nodeType":"ContractDefinition","src":"7418:174:0","nodes":[{"id":835,"nodeType":"VariableDeclaration","src":"7448:18:0","nodes":[],"constant":false,"mutability":"mutable","name":"v","nameLocation":"7465:1:0","scope":852,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":834,"name":"uint256","nodeType":"ElementaryTypeName","src":"7448:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"id":843,"nodeType":"FunctionDefinition","src":"7473:36:0","nodes":[],"body":{"id":842,"nodeType":"Block","src":"7487:22:0","nodes":[],"statements":[{"expression":{"id":840,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":838,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7497:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"31","id":839,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7501:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"7497:5:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":841,"nodeType":"ExpressionStatement","src":"7497:5:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":836,"nodeType":"ParameterList","parameters":[],"src":"7484:2:0"},"returnParameters":{"id":837,"nodeType":"ParameterList","parameters":[],"src":"7487:0:0"},"scope":852,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":851,"nodeType":"FunctionDefinition","src":"7515:75:0","nodes":[],"body":{"id":850,"nodeType":"Block","src":"7565:25:0","nodes":[],"statements":[{"expression":{"id":848,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7582:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":847,"id":849,"nodeType":"Return","src":"7575:8:0"}]},"functionSelector":"20965255","implemented":true,"kind":"function","modifiers":[],"name":"getValue","nameLocation":"7524:8:0","parameters":{"id":844,"nodeType":"ParameterList","parameters":[],"src":"7532:2:0"},"returnParameters":{"id":847,"nodeType":"ParameterList","parameters":[{"constant":false,"id":846,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":851,"src":"7556:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":845,"name":"uint256","nodeType":"ElementaryTypeName","src":"7556:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7555:9:0"},"scope":852,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkedContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[852],"name":"ForkedContract","nameLocation":"7427:14:0","scope":969,"usedErrors":[]},{"id":968,"nodeType":"ContractDefinition","src":"7594:813:0","nodes":[{"id":866,"nodeType":"VariableDeclaration","src":"7620:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7646:10:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":853,"name":"address","nodeType":"ElementaryTypeName","src":"7620:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":861,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7693:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":860,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7683:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":862,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7683:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":859,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7675:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":858,"name":"uint256","nodeType":"ElementaryTypeName","src":"7675:7:0","typeDescriptions":{}}},"id":863,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7675:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":857,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7667:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":856,"name":"uint160","nodeType":"ElementaryTypeName","src":"7667:7:0","typeDescriptions":{}}},"id":864,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7667:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":855,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7659:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":854,"name":"address","nodeType":"ElementaryTypeName","src":"7659:7:0","typeDescriptions":{}}},"id":865,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7659:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":872,"nodeType":"VariableDeclaration","src":"7720:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7741:2:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":868,"nodeType":"UserDefinedTypeName","pathNode":{"id":867,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7720:2:0"},"referencedDeclaration":83,"src":"7720:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":870,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":866,"src":"7749:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":869,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7746:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":871,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7746:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":967,"nodeType":"FunctionDefinition","src":"7767:638:0","nodes":[],"body":{"id":966,"nodeType":"Block","src":"7791:614:0","nodes":[],"statements":[{"assignments":[876],"declarations":[{"constant":false,"id":876,"mutability":"mutable","name":"testAddr","nameLocation":"7809:8:0","nodeType":"VariableDeclaration","scope":966,"src":"7801:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":875,"name":"address","nodeType":"ElementaryTypeName","src":"7801:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":884,"initialValue":{"arguments":[{"arguments":[{"hexValue":"307831323334","id":881,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7836:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":880,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7828:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":879,"name":"uint160","nodeType":"ElementaryTypeName","src":"7828:7:0","typeDescriptions":{}}},"id":882,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7828:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":878,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7820:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":877,"name":"address","nodeType":"ElementaryTypeName","src":"7820:7:0","typeDescriptions":{}}},"id":883,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7820:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"7801:43:0"},{"assignments":[887],"declarations":[{"constant":false,"id":887,"mutability":"mutable","name":"fc","nameLocation":"7869:2:0","nodeType":"VariableDeclaration","scope":966,"src":"7854:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"},"typeName":{"id":886,"nodeType":"UserDefinedTypeName","pathNode":{"id":885,"name":"ForkedContract","nodeType":"IdentifierPath","referencedDeclaration":852,"src":"7854:14:0"},"referencedDeclaration":852,"src":"7854:14:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"visibility":"internal"}],"id":891,"initialValue":{"arguments":[{"id":889,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7889:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":888,"name":"ForkedContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":852,"src":"7874:14:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ForkedContract_$852_$","typeString":"type(contract ForkedContract)"}},"id":890,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7874:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"nodeType":"VariableDeclarationStatement","src":"7854:44:0"},{"expression":{"arguments":[{"hexValue":"666f726b31","id":895,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7929:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},"value":"fork1"},{"hexValue":"3132333435","id":896,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7938:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"}],"expression":{"id":892,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7909:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":894,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"7909:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":897,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7909:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":898,"nodeType":"ExpressionStatement","src":"7909:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":905,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":902,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7974:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":900,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7962:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":901,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7962:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":903,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7962:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3132333435","id":904,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7987:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"},"src":"7962:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":906,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7994:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":899,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"7954:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":907,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7954:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":908,"nodeType":"ExpressionStatement","src":"7954:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":914,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":910,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8036:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":911,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8036:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":912,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8036:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31","id":913,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8053:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"8036:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652031","id":915,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8056:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""},"value":"value should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""}],"id":909,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8028:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":916,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8028:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":917,"nodeType":"ExpressionStatement","src":"8028:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":925,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":919,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8094:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":920,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8094:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"31","id":923,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8122:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"}],"id":922,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8114:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":921,"name":"uint256","nodeType":"ElementaryTypeName","src":"8114:7:0","typeDescriptions":{}}},"id":924,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8114:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8094:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652031","id":926,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8126:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""},"value":"balance should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""}],"id":918,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8086:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":927,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8086:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":928,"nodeType":"ExpressionStatement","src":"8086:62:0"},{"expression":{"arguments":[{"hexValue":"666f726b32","id":932,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8179:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},"value":"fork2"},{"hexValue":"3233343536","id":933,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8188:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"}],"expression":{"id":929,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8159:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":931,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"8159:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":934,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8159:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":935,"nodeType":"ExpressionStatement","src":"8159:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":939,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8224:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":937,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8212:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":938,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"8212:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":940,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8212:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3233343536","id":941,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8237:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"},"src":"8212:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":943,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8244:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":936,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8204:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":944,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8204:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":945,"nodeType":"ExpressionStatement","src":"8204:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":951,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":947,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8286:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":948,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8286:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":949,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8286:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"32","id":950,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8303:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"src":"8286:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652032","id":952,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8306:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""},"value":"value should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""}],"id":946,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8278:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":953,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8278:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":954,"nodeType":"ExpressionStatement","src":"8278:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":962,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":956,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8344:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":957,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8344:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"32","id":960,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8372:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"}],"id":959,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8364:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":958,"name":"uint256","nodeType":"ElementaryTypeName","src":"8364:7:0","typeDescriptions":{}}},"id":961,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8364:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8344:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652032","id":963,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8376:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""},"value":"balance should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""}],"id":955,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8336:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":964,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8336:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":965,"nodeType":"ExpressionStatement","src":"8336:62:0"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"7776:3:0","parameters":{"id":873,"nodeType":"ParameterList","parameters":[],"src":"7779:2:0"},"returnParameters":{"id":874,"nodeType":"ParameterList","parameters":[],"src":"7791:0:0"},"scope":968,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkTester","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[968],"name":"ForkTester","nameLocation":"7603:10:0","scope":969,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ScriptExample.json b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ScriptExample.json index 09a0bf47f02db..0fd610c4725cf 100644 --- a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ScriptExample.json +++ b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/ScriptExample.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"call1","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"call2","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"callPure","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"pure"},{"type":"function","name":"counter","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"hello","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"view"},{"type":"function","name":"nested1","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"nested2","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"run","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"runBroadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b5061202a806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c8063a76ccdfa11610076578063bef03abc1161005b578063bef03abc14610111578063c040622614610119578063dbf1282f146100c357600080fd5b8063a76ccdfa146100eb578063a777d0dc146100fe57600080fd5b806361bc221a146100a85780637e79255d146100c35780637f8b915c146100d85780638d3ef7ca146100c3575b600080fd5b6100b160005481565b60405190815260200160405180910390f35b6100d66100d1366004611a60565b610121565b005b6100d66100e6366004611a60565b610178565b6100d66100f9366004611a60565b6101b7565b6100d661010c366004611a60565b61023f565b6100d66102bd565b6100d6610f54565b60008054908061013083611ad2565b919050555061017482828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061175b92505050565b5050565b61017482828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061175b92505050565b6000805490806101c683611ad2565b90915550506040517fdbf1282f000000000000000000000000000000000000000000000000000000008152309063dbf1282f906102099085908590600401611b31565b600060405180830381600087803b15801561022357600080fd5b505af1158015610237573d6000803e3d6000fd5b505050505050565b61027e82828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061175b92505050565b6101746040518060400160405280601081526020017f68656c6c6f206d73672e73656e64657200000000000000000000000000000000815250336117ed565b604080518082018252600b81527f6e6f6e6365207374617274000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526103909190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab906024015b602060405180830381865afa15801561035d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103819190611b7e565b67ffffffffffffffff1661187e565b6103ce6040518060400160405280600e81526020017f74657374696e672073696e676c6500000000000000000000000000000000000081525061175b565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561043957600080fd5b505af115801561044d573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3100000000000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156104d057600080fd5b505af11580156104e4573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3200000000000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561056757600080fd5b505af115801561057b573d6000803e3d6000fd5b505050506105bd6040518060400160405280601281526020017f74657374696e672073746172742f73746f70000000000000000000000000000081525061175b565b6040517f7fec2a8d00000000000000000000000000000000000000000000000000000000815262c0ffee6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b15801561062557600080fd5b505af1158015610639573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3100000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156106bc57600080fd5b505af11580156106d0573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3200000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561075357600080fd5b505af1158015610767573d6000803e3d6000fd5b50506040517f7f8b915c00000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f737461727473746f705f707572650000000000000000000000000000000000006044820152309250637f8b915c915060640160006040518083038186803b1580156107e857600080fd5b505afa1580156107fc573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561086b57600080fd5b505af115801561087f573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3300000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b15801561090257600080fd5b505af1158015610916573d6000803e3d6000fd5b505050506109586040518060400160405280600e81526020017f74657374696e67206e657374656400000000000000000000000000000000000081525061175b565b6040517f7fec2a8d0000000000000000000000000000000000000000000000000000000081526112346004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b1580156109bf57600080fd5b505af11580156109d3573d6000803e3d6000fd5b50506040517fa76ccdfa00000000000000000000000000000000000000000000000000000000815260206004820152600660248201527f6e65737465640000000000000000000000000000000000000000000000000000604482015230925063a76ccdfa9150606401600060405180830381600087803b158015610a5657600080fd5b505af1158015610a6a573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610ad957600080fd5b505af1158015610aed573d6000803e3d6000fd5b50505050610b2f6040518060400160405280601381526020017f636f6e7472616374206465706c6f796d656e740000000000000000000000000081525061175b565b6040517fe6962cdb000000000000000000000000000000000000000000000000000000008152621234566004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610b9757600080fd5b505af1158015610bab573d6000803e3d6000fd5b5050505060006104d2604051610bc090611a54565b908152602001604051809103906000f080158015610be2573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c549190611baf565b6104d214610c6157600080fd5b610c9f6040518060400160405280600881526020017f637265617465203200000000000000000000000000000000000000000000000081525061175b565b6040517fe6962cdb00000000000000000000000000000000000000000000000000000000815261cafe6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610d0657600080fd5b505af1158015610d1a573d6000803e3d6000fd5b505050506000602a60001b6104d2604051610d3490611a54565b9081526020018190604051809103906000f5905080158015610d5a573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610da8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dcc9190611baf565b6104d214610dd957600080fd5b610e176040518060400160405280600581526020017f646f6e652100000000000000000000000000000000000000000000000000000081525061175b565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610e8257600080fd5b505af1158015610e96573d6000803e3d6000fd5b505050506104d2604051610ea990611a54565b908152602001604051809103906000f080158015610ecb573d6000803e3d6000fd5b5050604080518082018252600981527f6e6f6e636520656e640000000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526101749190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b604080517f4777f3cf0000000000000000000000000000000000000000000000000000000081526004810191909152600c60448201527f4558414d504c455f424f4f4c0000000000000000000000000000000000000000606482015260006024820181905290737109709ecfa91a80626ff3989d68f67f5b1dd12d90634777f3cf90608401602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611bc8565b905061105b6040518060400160405280601381526020017f626f6f6c2076616c75652066726f6d20656e76000000000000000000000000008152508261190f565b61109a6040518060400160405280600d81526020017f636f6e7472616374206164647200000000000000000000000000000000000000815250306117ed565b604080518082018252600e81527f636f6e7472616374206e6f6e6365000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526111219190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b6111606040518060400160405280600b81526020017f73656e6465722061646472000000000000000000000000000000000000000000815250336117ed565b604080518082018252600c81527f73656e646572206e6f6e63650000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523360048201526111e79190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b60408051808201825260208082527f7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d9082015290517f213e4198000000000000000000000000000000000000000000000000000000008152600090737109709ecfa91a80626ff3989d68f67f5b1dd12d9063213e41989061126c908590600401611c64565b600060405180830381865afa158015611289573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526112cf9190810190611d34565b90506113456040518060400160405280600481526020017f6b657973000000000000000000000000000000000000000000000000000000008152508260008151811061131d5761131d611e68565b60200260200101518360018151811061133857611338611e68565b60200260200101516119a0565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f66726f6d206f726967696e616c000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156113c257600080fd5b505afa1580156113d6573d6000803e3d6000fd5b50506040517f06447d5600000000000000000000000000000000000000000000000000000000815260426004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d92506306447d569150602401600060405180830381600087803b15801561144057600080fd5b505af1158015611454573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b20310000000000000000000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b1580156114d557600080fd5b505afa1580156114e9573d6000803e3d6000fd5b5050505061152c6040518060400160405280601781526020017f706172656e742073636f7065206d73672e73656e646572000000000000000000815250336117ed565b61156b6040518060400160405280601a81526020017f706172656e742073636f706520636f6e74726163742e61646472000000000000815250306117ed565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b203200000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156115e857600080fd5b505afa1580156115fc573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166390c5013b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561166b57600080fd5b505af115801561167f573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f66726f6d206f726967696e616c20616761696e00000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b15801561170057600080fd5b505afa158015611714573d6000803e3d6000fd5b505050506117566040518060400160405280600581526020017f646f6e652100000000000000000000000000000000000000000000000000000081525061175b565b505050565b6117ea8160405160240161176f9190611e97565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f41304fac00000000000000000000000000000000000000000000000000000000179052611a2f565b50565b6101748282604051602401611803929190611eaa565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f319af33300000000000000000000000000000000000000000000000000000000179052611a2f565b6101748282604051602401611894929190611ee2565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fb60e72cc00000000000000000000000000000000000000000000000000000000179052611a2f565b6101748282604051602401611925929190611f04565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc3b5563500000000000000000000000000000000000000000000000000000000179052611a2f565b6117568383836040516024016119b893929190611f28565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f2ced7cef000000000000000000000000000000000000000000000000000000001790525b6117ea8180516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60b280611f6c83390190565b60008060208385031215611a7357600080fd5b823567ffffffffffffffff80821115611a8b57600080fd5b818501915085601f830112611a9f57600080fd5b813581811115611aae57600080fd5b866020828501011115611ac057600080fd5b60209290920196919550909350505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611b2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b600060208284031215611b9057600080fd5b815167ffffffffffffffff81168114611ba857600080fd5b9392505050565b600060208284031215611bc157600080fd5b5051919050565b600060208284031215611bda57600080fd5b81518015158114611ba857600080fd5b60005b83811015611c05578181015183820152602001611bed565b83811115611c14576000848401525b50505050565b60008151808452611c32816020860160208601611bea565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b604081526000611c776040830184611c1a565b8281036020840152600c81527f2e726f6f745f6b65795b305d000000000000000000000000000000000000000060208201526040810191505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611d2c57611d2c611cb6565b604052919050565b60006020808385031215611d4757600080fd5b825167ffffffffffffffff80821115611d5f57600080fd5b8185019150601f8681840112611d7457600080fd5b825182811115611d8657611d86611cb6565b8060051b611d95868201611ce5565b918252848101860191868101908a841115611daf57600080fd5b87870192505b83831015611e5a57825186811115611dcd5760008081fd5b8701603f81018c13611ddf5760008081fd5b88810151604088821115611df557611df5611cb6565b611e248b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08a85011601611ce5565b8281528e82848601011115611e395760008081fd5b611e48838d8301848701611bea565b85525050509187019190870190611db5565b9a9950505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602081526000611ba86020830184611c1a565b604081526000611ebd6040830185611c1a565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b604081526000611ef56040830185611c1a565b90508260208301529392505050565b604081526000611f176040830185611c1a565b905082151560208301529392505050565b606081526000611f3b6060830186611c1a565b8281036020840152611f4d8186611c1a565b90508281036040840152611f618185611c1a565b969550505050505056fe608060405234801561001057600080fd5b506040516100b23803806100b283398101604081905261002f91610037565b600055610050565b60006020828403121561004957600080fd5b5051919050565b60548061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000aa164736f6c634300080f000a","sourceMap":"2541:3359:0:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b50600436106100a35760003560e01c8063a76ccdfa11610076578063bef03abc1161005b578063bef03abc14610111578063c040622614610119578063dbf1282f146100c357600080fd5b8063a76ccdfa146100eb578063a777d0dc146100fe57600080fd5b806361bc221a146100a85780637e79255d146100c35780637f8b915c146100d85780638d3ef7ca146100c3575b600080fd5b6100b160005481565b60405190815260200160405180910390f35b6100d66100d1366004611a60565b610121565b005b6100d66100e6366004611a60565b610178565b6100d66100f9366004611a60565b6101b7565b6100d661010c366004611a60565b61023f565b6100d66102bd565b6100d6610f54565b60008054908061013083611ad2565b919050555061017482828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061175b92505050565b5050565b61017482828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061175b92505050565b6000805490806101c683611ad2565b90915550506040517fdbf1282f000000000000000000000000000000000000000000000000000000008152309063dbf1282f906102099085908590600401611b31565b600060405180830381600087803b15801561022357600080fd5b505af1158015610237573d6000803e3d6000fd5b505050505050565b61027e82828080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061175b92505050565b6101746040518060400160405280601081526020017f68656c6c6f206d73672e73656e64657200000000000000000000000000000000815250336117ed565b604080518082018252600b81527f6e6f6e6365207374617274000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526103909190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab906024015b602060405180830381865afa15801561035d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103819190611b7e565b67ffffffffffffffff1661187e565b6103ce6040518060400160405280600e81526020017f74657374696e672073696e676c6500000000000000000000000000000000000081525061175b565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561043957600080fd5b505af115801561044d573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3100000000000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156104d057600080fd5b505af11580156104e4573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3200000000000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561056757600080fd5b505af115801561057b573d6000803e3d6000fd5b505050506105bd6040518060400160405280601281526020017f74657374696e672073746172742f73746f70000000000000000000000000000081525061175b565b6040517f7fec2a8d00000000000000000000000000000000000000000000000000000000815262c0ffee6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b15801561062557600080fd5b505af1158015610639573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3100000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156106bc57600080fd5b505af11580156106d0573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3200000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561075357600080fd5b505af1158015610767573d6000803e3d6000fd5b50506040517f7f8b915c00000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f737461727473746f705f707572650000000000000000000000000000000000006044820152309250637f8b915c915060640160006040518083038186803b1580156107e857600080fd5b505afa1580156107fc573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561086b57600080fd5b505af115801561087f573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3300000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b15801561090257600080fd5b505af1158015610916573d6000803e3d6000fd5b505050506109586040518060400160405280600e81526020017f74657374696e67206e657374656400000000000000000000000000000000000081525061175b565b6040517f7fec2a8d0000000000000000000000000000000000000000000000000000000081526112346004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b1580156109bf57600080fd5b505af11580156109d3573d6000803e3d6000fd5b50506040517fa76ccdfa00000000000000000000000000000000000000000000000000000000815260206004820152600660248201527f6e65737465640000000000000000000000000000000000000000000000000000604482015230925063a76ccdfa9150606401600060405180830381600087803b158015610a5657600080fd5b505af1158015610a6a573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610ad957600080fd5b505af1158015610aed573d6000803e3d6000fd5b50505050610b2f6040518060400160405280601381526020017f636f6e7472616374206465706c6f796d656e740000000000000000000000000081525061175b565b6040517fe6962cdb000000000000000000000000000000000000000000000000000000008152621234566004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610b9757600080fd5b505af1158015610bab573d6000803e3d6000fd5b5050505060006104d2604051610bc090611a54565b908152602001604051809103906000f080158015610be2573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c549190611baf565b6104d214610c6157600080fd5b610c9f6040518060400160405280600881526020017f637265617465203200000000000000000000000000000000000000000000000081525061175b565b6040517fe6962cdb00000000000000000000000000000000000000000000000000000000815261cafe6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610d0657600080fd5b505af1158015610d1a573d6000803e3d6000fd5b505050506000602a60001b6104d2604051610d3490611a54565b9081526020018190604051809103906000f5905080158015610d5a573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610da8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dcc9190611baf565b6104d214610dd957600080fd5b610e176040518060400160405280600581526020017f646f6e652100000000000000000000000000000000000000000000000000000081525061175b565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610e8257600080fd5b505af1158015610e96573d6000803e3d6000fd5b505050506104d2604051610ea990611a54565b908152602001604051809103906000f080158015610ecb573d6000803e3d6000fd5b5050604080518082018252600981527f6e6f6e636520656e640000000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526101749190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b604080517f4777f3cf0000000000000000000000000000000000000000000000000000000081526004810191909152600c60448201527f4558414d504c455f424f4f4c0000000000000000000000000000000000000000606482015260006024820181905290737109709ecfa91a80626ff3989d68f67f5b1dd12d90634777f3cf90608401602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611bc8565b905061105b6040518060400160405280601381526020017f626f6f6c2076616c75652066726f6d20656e76000000000000000000000000008152508261190f565b61109a6040518060400160405280600d81526020017f636f6e7472616374206164647200000000000000000000000000000000000000815250306117ed565b604080518082018252600e81527f636f6e7472616374206e6f6e6365000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526111219190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b6111606040518060400160405280600b81526020017f73656e6465722061646472000000000000000000000000000000000000000000815250336117ed565b604080518082018252600c81527f73656e646572206e6f6e63650000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523360048201526111e79190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b60408051808201825260208082527f7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d9082015290517f213e4198000000000000000000000000000000000000000000000000000000008152600090737109709ecfa91a80626ff3989d68f67f5b1dd12d9063213e41989061126c908590600401611c64565b600060405180830381865afa158015611289573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526112cf9190810190611d34565b90506113456040518060400160405280600481526020017f6b657973000000000000000000000000000000000000000000000000000000008152508260008151811061131d5761131d611e68565b60200260200101518360018151811061133857611338611e68565b60200260200101516119a0565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f66726f6d206f726967696e616c000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156113c257600080fd5b505afa1580156113d6573d6000803e3d6000fd5b50506040517f06447d5600000000000000000000000000000000000000000000000000000000815260426004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d92506306447d569150602401600060405180830381600087803b15801561144057600080fd5b505af1158015611454573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b20310000000000000000000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b1580156114d557600080fd5b505afa1580156114e9573d6000803e3d6000fd5b5050505061152c6040518060400160405280601781526020017f706172656e742073636f7065206d73672e73656e646572000000000000000000815250336117ed565b61156b6040518060400160405280601a81526020017f706172656e742073636f706520636f6e74726163742e61646472000000000000815250306117ed565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b203200000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156115e857600080fd5b505afa1580156115fc573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166390c5013b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561166b57600080fd5b505af115801561167f573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f66726f6d206f726967696e616c20616761696e00000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b15801561170057600080fd5b505afa158015611714573d6000803e3d6000fd5b505050506117566040518060400160405280600581526020017f646f6e652100000000000000000000000000000000000000000000000000000081525061175b565b505050565b6117ea8160405160240161176f9190611e97565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f41304fac00000000000000000000000000000000000000000000000000000000179052611a2f565b50565b6101748282604051602401611803929190611eaa565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f319af33300000000000000000000000000000000000000000000000000000000179052611a2f565b6101748282604051602401611894929190611ee2565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fb60e72cc00000000000000000000000000000000000000000000000000000000179052611a2f565b6101748282604051602401611925929190611f04565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc3b5563500000000000000000000000000000000000000000000000000000000179052611a2f565b6117568383836040516024016119b893929190611f28565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f2ced7cef000000000000000000000000000000000000000000000000000000001790525b6117ea8180516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60b280611f6c83390190565b60008060208385031215611a7357600080fd5b823567ffffffffffffffff80821115611a8b57600080fd5b818501915085601f830112611a9f57600080fd5b813581811115611aae57600080fd5b866020828501011115611ac057600080fd5b60209290920196919550909350505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611b2a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b600060208284031215611b9057600080fd5b815167ffffffffffffffff81168114611ba857600080fd5b9392505050565b600060208284031215611bc157600080fd5b5051919050565b600060208284031215611bda57600080fd5b81518015158114611ba857600080fd5b60005b83811015611c05578181015183820152602001611bed565b83811115611c14576000848401525b50505050565b60008151808452611c32816020860160208601611bea565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b604081526000611c776040830184611c1a565b8281036020840152600c81527f2e726f6f745f6b65795b305d000000000000000000000000000000000000000060208201526040810191505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611d2c57611d2c611cb6565b604052919050565b60006020808385031215611d4757600080fd5b825167ffffffffffffffff80821115611d5f57600080fd5b8185019150601f8681840112611d7457600080fd5b825182811115611d8657611d86611cb6565b8060051b611d95868201611ce5565b918252848101860191868101908a841115611daf57600080fd5b87870192505b83831015611e5a57825186811115611dcd5760008081fd5b8701603f81018c13611ddf5760008081fd5b88810151604088821115611df557611df5611cb6565b611e248b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08a85011601611ce5565b8281528e82848601011115611e395760008081fd5b611e48838d8301848701611bea565b85525050509187019190870190611db5565b9a9950505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602081526000611ba86020830184611c1a565b604081526000611ebd6040830185611c1a565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b604081526000611ef56040830185611c1a565b90508260208301529392505050565b604081526000611f176040830185611c1a565b905082151560208301529392505050565b606081526000611f3b6060830186611c1a565b8281036020840152611f4d8186611c1a565b90508281036040840152611f618185611c1a565b969550505050505056fe608060405234801561001057600080fd5b506040516100b23803806100b283398101604081905261002f91610037565b600055610050565b60006020828403121561004957600080fd5b5051919050565b60548061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000aa164736f6c634300080f000a","sourceMap":"2541:3359:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2775:22;;;;;;;;;160:25:1;;;148:2;133:18;2775:22:0;;;;;;;5405:95;;;;;;:::i;:::-;;:::i;:::-;;5814:84;;;;;;:::i;:::-;;:::i;5607:98::-;;;;;;:::i;:::-;;:::i;5256:143::-;;;;;;:::i;:::-;;:::i;3903:1258::-;;;:::i;2887:949::-;;;:::i;5405:95::-;5459:7;:9;;;:7;:9;;;:::i;:::-;;;;;;5478:15;5490:2;;5478:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5478:11:0;;-1:-1:-1;;;5478:15:0:i;:::-;5405:95;;:::o;5814:84::-;5876:15;5888:2;;5876:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5876:11:0;;-1:-1:-1;;;5876:15:0:i;5607:98::-;5663:7;:9;;;:7;:9;;;:::i;:::-;;;;-1:-1:-1;;5682:16:0;;;;;:4;;:12;;:16;;5695:2;;;;5682:16;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5607:98;;:::o;5256:143::-;5315:15;5327:2;;5315:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5315:11:0;;-1:-1:-1;;;5315:15:0:i;:::-;5340:52;;;;;;;;;;;;;;;;;;5380:10;5340:11;:52::i;3903:1258::-;3944:63;;;;;;;;;;;;;;;;3979:26;;;;;3999:4;3979:26;;;1747:74:1;3944:63:0;;;3979:11;;;;1720:18:1;;3979:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3971:35;;3944:11;:63::i;:::-;4018:29;;;;;;;;;;;;;;;;;;:11;:29::i;:::-;2634:28;2626:37;;4057:12;;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4081:26:0;;;;;2327:2:1;4081:26:0;;;2309:21:1;2366:2;2346:18;;;2339:30;2405:14;2385:18;;;2378:42;4081:4:0;;-1:-1:-1;4081:10:0;;-1:-1:-1;2437:18:1;;4081:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4117:26:0;;;;;2668:2:1;4117:26:0;;;2650:21:1;2707:2;2687:18;;;2680:30;2746:14;2726:18;;;2719:42;4117:4:0;;-1:-1:-1;4117:10:0;;-1:-1:-1;2778:18:1;;4117:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4154:33;;;;;;;;;;;;;;;;;;:11;:33::i;:::-;4197:45;;;;;4231:8;4197:45;;;1747:74:1;4197:17:0;;;;1720:18:1;;4197:45:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4252:29:0;;;;;3009:2:1;4252:29:0;;;2991:21:1;3048:2;3028:18;;;3021:30;3087:17;3067:18;;;3060:45;4252:4:0;;-1:-1:-1;4252:10:0;;-1:-1:-1;3122:18:1;;4252:29:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4291:29:0;;;;;3353:2:1;4291:29:0;;;3335:21:1;3392:2;3372:18;;;3365:30;3431:17;3411:18;;;3404:45;4291:4:0;;-1:-1:-1;4291:10:0;;-1:-1:-1;3466:18:1;;4291:29:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4330:31:0;;;;;3697:2:1;4330:31:0;;;3679:21:1;3736:2;3716:18;;;3709:30;3775:16;3755:18;;;3748:44;4330:4:0;;-1:-1:-1;4330:13:0;;-1:-1:-1;3809:18:1;;4330:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2634:28;2626:37;;4371:16;;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4399:29:0;;;;;4040:2:1;4399:29:0;;;4022:21:1;4079:2;4059:18;;;4052:30;4118:17;4098:18;;;4091:45;4399:4:0;;-1:-1:-1;4399:10:0;;-1:-1:-1;4153:18:1;;4399:29:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4439;;;;;;;;;;;;;;;;;;:11;:29::i;:::-;4478:43;;;;;4512:6;4478:43;;;1747:74:1;4478:17:0;;;;1720:18:1;;4478:43:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4531:22:0;;;;;4384:2:1;4531:22:0;;;4366:21:1;4423:1;4403:18;;;4396:29;4461:8;4441:18;;;4434:36;4531:4:0;;-1:-1:-1;4531:12:0;;-1:-1:-1;4487:18:1;;4531:22:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2634:28;2626:37;;4563:16;;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4592:34;;;;;;;;;;;;;;;;;;:11;:34::i;:::-;4636:40;;;;;4665:8;4636:40;;;1747:74:1;4636:12:0;;;;1720:18:1;;4636:40:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4686:8;4708:4;4697:16;;;;;:::i;:::-;160:25:1;;;148:2;133:18;4697:16:0;;;;;;;;;;;;;;;;;;;;;;;4686:27;;4731:1;:5;;;:7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4742:4;4731:15;4723:24;;;;;;4758:23;;;;;;;;;;;;;;;;;;:11;:23::i;:::-;4791:38;;;;;4820:6;4791:38;;;1747:74:1;4791:12:0;;;;1720:18:1;;4791:38:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4839:8;4883:2;4867:20;;4889:4;4850:44;;;;;:::i;:::-;160:25:1;;;148:2;133:18;4850:44:0;;;;;;;;;;;;;;;;;;;;;;;;;;;4839:55;;4912:1;:5;;;:7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4923:4;4912:15;4904:24;;;;;;4938:20;;;;;;;;;;;;;;;;;;:11;:20::i;:::-;2634:28;2626:37;;5042:12;;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5077:4;5066:16;;;;;:::i;:::-;160:25:1;;;148:2;133:18;5066:16:0;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5093:61:0;;;;;;;;;;;;;;;;5126:26;;;;;5146:4;5126:26;;;1747:74:1;5093:61:0;;;5126:11;;;;1720:18:1;;5126:26:0;1601:226:1;2887:949:0;2928:31;;;;;;;;;5104:21:1;;;;5161:2;5141:18;;;5134:30;5200:14;5180:18;;;5173:42;2919:6:0;5267:20:1;;;5260:52;;;2919:6:0;2928:8;;;;5232:19:1;;2928:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2919:40;;2969:37;;;;;;;;;;;;;;;;;;3004:1;2969:11;:37::i;:::-;3017:43;;;;;;;;;;;;;;;;;;3054:4;3017:11;:43::i;:::-;3070:57;;;;;;;;;;;;;;;;3100:26;;;;;3120:4;3100:26;;;1747:74:1;3070:57:0;;;3100:11;;;;1720:18:1;;3100:26:0;1601:226:1;3070:57:0;3137:47;;;;;;;;;;;;;;;;;;3172:10;3137:11;:47::i;:::-;3194:61;;;;;;;;;;;;;;;;3222:32;;;;;3242:10;3222:32;;;1747:74:1;3194:61:0;;;3222:11;;;;1720:18:1;;3222:32:0;1601:226:1;3194:61:0;3266:55;;;;;;;;;;;;;;;;;3354:38;;;;;3266:18;;3354:16;;;;:38;;3266:55;;3354:38;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3331:61;;3402:37;;;;;;;;;;;;;;;;;;3422:4;3427:1;3422:7;;;;;;;;:::i;:::-;;;;;;;3431:4;3436:1;3431:7;;;;;;;;:::i;:::-;;;;;;;3402:11;:37::i;:::-;3450:27;;;;;9413:2:1;3450:27:0;;;9395:21:1;9452:2;9432:18;;;9425:30;9491:15;9471:18;;;9464:43;3450:4:0;;:10;;9524:18:1;;3450:27:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3487:37:0;;;;;3517:4;3487:37;;;1747:74:1;3487:13:0;;-1:-1:-1;3487:13:0;;-1:-1:-1;1720:18:1;;3487:37:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3534:26:0;;;;;9755:2:1;3534:26:0;;;9737:21:1;9794:2;9774:18;;;9767:30;9833:14;9813:18;;;9806:42;3534:4:0;;-1:-1:-1;3534:10:0;;-1:-1:-1;9865:18:1;;3534:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3570:59;;;;;;;;;;;;;;;;;;3617:10;3570:11;:59::i;:::-;3639:56;;;;;;;;;;;;;;;;;;3689:4;3639:11;:56::i;:::-;3705:26;;;;;10096:2:1;3705:26:0;;;10078:21:1;10135:2;10115:18;;;10108:30;10174:14;10154:18;;;10147:42;3705:4:0;;:10;;10206:18:1;;3705:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2634:28;2626:37;;3741:12;;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3765:33:0;;;;;10437:2:1;3765:33:0;;;10419:21:1;10476:2;10456:18;;;10449:30;10515:21;10495:18;;;10488:49;3765:4:0;;-1:-1:-1;3765:10:0;;-1:-1:-1;10554:18:1;;3765:33:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3809:20;;;;;;;;;;;;;;;;;;:11;:20::i;:::-;2909:927;;;2887:949::o;1658:121::-;1713:59;1768:2;1729:42;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1713:15;:59::i;:::-;1658:121;:::o;2081:145::-;2148:71;2211:2;2215;2164:54;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;2148:15;:71::i;1930:145::-;1997:71;2060:2;2064;2013:54;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1997:15;:71::i;1785:139::-;1849:68;1909:2;1913;1865:51;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1849:15;:68::i;2232:179::-;2323:81;2392:2;2396;2400;2339:64;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1133:133;1204:55;1251:7;1370:14;;856:42;1543:2;1530:16;;1346:21;;1370:14;1530:16;856:42;1579:5;1568:68;1559:77;;1496:150;;1272:380;:::o;-1:-1:-1:-;;;;;;;;:::o;196:592:1:-;267:6;275;328:2;316:9;307:7;303:23;299:32;296:52;;;344:1;341;334:12;296:52;384:9;371:23;413:18;454:2;446:6;443:14;440:34;;;470:1;467;460:12;440:34;508:6;497:9;493:22;483:32;;553:7;546:4;542:2;538:13;534:27;524:55;;575:1;572;565:12;524:55;615:2;602:16;641:2;633:6;630:14;627:34;;;657:1;654;647:12;627:34;702:7;697:2;688:6;684:2;680:15;676:24;673:37;670:57;;;723:1;720;713:12;670:57;754:2;746:11;;;;;776:6;;-1:-1:-1;196:592:1;;-1:-1:-1;;;;196:592:1:o;793:349::-;832:3;863:66;856:5;853:77;850:257;;963:77;960:1;953:88;1064:4;1061:1;1054:15;1092:4;1089:1;1082:15;850:257;-1:-1:-1;1134:1:1;1123:13;;793:349::o;1147:449::-;1306:2;1295:9;1288:21;1345:6;1340:2;1329:9;1325:18;1318:34;1402:6;1394;1389:2;1378:9;1374:18;1361:48;1458:1;1429:22;;;1453:2;1425:31;;;1418:42;;;;1512:2;1500:15;;;1517:66;1496:88;1481:104;1477:113;;1147:449;-1:-1:-1;1147:449:1:o;1832:288::-;1901:6;1954:2;1942:9;1933:7;1929:23;1925:32;1922:52;;;1970:1;1967;1960:12;1922:52;2002:9;1996:16;2052:18;2045:5;2041:30;2034:5;2031:41;2021:69;;2086:1;2083;2076:12;2021:69;2109:5;1832:288;-1:-1:-1;;;1832:288:1:o;4709:184::-;4779:6;4832:2;4820:9;4811:7;4807:23;4803:32;4800:52;;;4848:1;4845;4838:12;4800:52;-1:-1:-1;4871:16:1;;4709:184;-1:-1:-1;4709:184:1:o;5323:277::-;5390:6;5443:2;5431:9;5422:7;5418:23;5414:32;5411:52;;;5459:1;5456;5449:12;5411:52;5491:9;5485:16;5544:5;5537:13;5530:21;5523:5;5520:32;5510:60;;5566:1;5563;5556:12;5605:258;5677:1;5687:113;5701:6;5698:1;5695:13;5687:113;;;5777:11;;;5771:18;5758:11;;;5751:39;5723:2;5716:10;5687:113;;;5818:6;5815:1;5812:13;5809:48;;;5853:1;5844:6;5839:3;5835:16;5828:27;5809:48;;5605:258;;;:::o;5868:317::-;5910:3;5948:5;5942:12;5975:6;5970:3;5963:19;5991:63;6047:6;6040:4;6035:3;6031:14;6024:4;6017:5;6013:16;5991:63;:::i;:::-;6099:2;6087:15;6104:66;6083:88;6074:98;;;;6174:4;6070:109;;5868:317;-1:-1:-1;;5868:317:1:o;6190:493::-;6440:2;6429:9;6422:21;6403:4;6466:45;6507:2;6496:9;6492:18;6484:6;6466:45;:::i;:::-;6559:9;6551:6;6547:22;6542:2;6531:9;6527:18;6520:50;6594:2;6586:6;6579:18;6630:14;6625:2;6617:6;6613:15;6606:39;6674:2;6666:6;6662:15;6654:23;;;6190:493;;;;:::o;6688:184::-;6740:77;6737:1;6730:88;6837:4;6834:1;6827:15;6861:4;6858:1;6851:15;6877:334;6948:2;6942:9;7004:2;6994:13;;7009:66;6990:86;6978:99;;7107:18;7092:34;;7128:22;;;7089:62;7086:88;;;7154:18;;:::i;:::-;7190:2;7183:22;6877:334;;-1:-1:-1;6877:334:1:o;7216:1801::-;7321:6;7352:2;7395;7383:9;7374:7;7370:23;7366:32;7363:52;;;7411:1;7408;7401:12;7363:52;7444:9;7438:16;7473:18;7514:2;7506:6;7503:14;7500:34;;;7530:1;7527;7520:12;7500:34;7568:6;7557:9;7553:22;7543:32;;7594:4;7634:7;7629:2;7625;7621:11;7617:25;7607:53;;7656:1;7653;7646:12;7607:53;7685:2;7679:9;7707:2;7703;7700:10;7697:36;;;7713:18;;:::i;:::-;7759:2;7756:1;7752:10;7782:28;7806:2;7802;7798:11;7782:28;:::i;:::-;7844:15;;;7914:11;;;7910:20;;;7875:12;;;;7942:19;;;7939:39;;;7974:1;7971;7964:12;7939:39;8006:2;8002;7998:11;7987:22;;8018:969;8034:6;8029:3;8026:15;8018:969;;;8113:3;8107:10;8149:2;8136:11;8133:19;8130:109;;;8193:1;8222:2;8218;8211:14;8130:109;8262:20;;8317:2;8309:11;;8305:25;-1:-1:-1;8295:123:1;;8372:1;8401:2;8397;8390:14;8295:123;8456:2;8452;8448:11;8442:18;8484:2;8510;8505:3;8502:11;8499:37;;;8516:18;;:::i;:::-;8562:111;8669:2;8600:66;8595:2;8590:3;8586:12;8582:85;8578:94;8562:111;:::i;:::-;8700:3;8693:5;8686:18;8747:7;8741:3;8735;8731:2;8727:12;8723:22;8720:35;8717:128;;;8797:1;8827:3;8822;8815:16;8717:128;8858:56;8910:3;8905:2;8898:5;8894:14;8888:3;8884:2;8880:12;8858:56;:::i;:::-;8927:18;;-1:-1:-1;;;8051:12:1;;;;8965;;;;8018:969;;;9006:5;7216:1801;-1:-1:-1;;;;;;;;;;7216:1801:1:o;9022:184::-;9074:77;9071:1;9064:88;9171:4;9168:1;9161:15;9195:4;9192:1;9185:15;10583:220;10732:2;10721:9;10714:21;10695:4;10752:45;10793:2;10782:9;10778:18;10770:6;10752:45;:::i;10808:340::-;10985:2;10974:9;10967:21;10948:4;11005:45;11046:2;11035:9;11031:18;11023:6;11005:45;:::i;:::-;10997:53;;11098:42;11090:6;11086:55;11081:2;11070:9;11066:18;11059:83;10808:340;;;;;:::o;11153:291::-;11330:2;11319:9;11312:21;11293:4;11350:45;11391:2;11380:9;11376:18;11368:6;11350:45;:::i;:::-;11342:53;;11431:6;11426:2;11415:9;11411:18;11404:34;11153:291;;;;;:::o;11449:301::-;11620:2;11609:9;11602:21;11583:4;11640:45;11681:2;11670:9;11666:18;11658:6;11640:45;:::i;:::-;11632:53;;11735:6;11728:14;11721:22;11716:2;11705:9;11701:18;11694:50;11449:301;;;;;:::o;11755:546::-;12000:2;11989:9;11982:21;11963:4;12026:45;12067:2;12056:9;12052:18;12044:6;12026:45;:::i;:::-;12119:9;12111:6;12107:22;12102:2;12091:9;12087:18;12080:50;12153:33;12179:6;12171;12153:33;:::i;:::-;12139:47;;12234:9;12226:6;12222:22;12217:2;12206:9;12202:18;12195:50;12262:33;12288:6;12280;12262:33;:::i;:::-;12254:41;11755:546;-1:-1:-1;;;;;;11755:546:1:o","linkReferences":{}},"methodIdentifiers":{"call1(string)":"7e79255d","call2(string)":"8d3ef7ca","callPure(string)":"7f8b915c","counter()":"61bc221a","hello(string)":"a777d0dc","nested1(string)":"a76ccdfa","nested2(string)":"dbf1282f","run()":"c0406226","runBroadcast()":"bef03abc"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"call1\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"call2\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"callPure\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counter\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"hello\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"nested1\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"nested2\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"run\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"runBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"title\":\"ScriptExample\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"hello(string)\":{\"notice\":\"example external function, to force a CALL, and test vm.startPrank with.\"},\"run()\":{\"notice\":\"example function, runs through basic cheat-codes and console logs.\"},\"runBroadcast()\":{\"notice\":\"example function, to test vm.broadcast with.\"}},\"notice\":\"ScriptExample is an example script. The Go forge script code tests that it can run this.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"ScriptExample\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3\",\"dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"call1"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"call2"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"pure","type":"function","name":"callPure"},{"inputs":[],"stateMutability":"view","type":"function","name":"counter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"view","type":"function","name":"hello"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"nested1"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"nested2"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"run"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"runBroadcast"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{"hello(string)":{"notice":"example external function, to force a CALL, and test vm.startPrank with."},"run()":{"notice":"example function, runs through basic cheat-codes and console logs."},"runBroadcast()":{"notice":"example function, to test vm.broadcast with."}},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"ScriptExample"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d","urls":["bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3","dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[{"astId":215,"contract":"scripts/ScriptExample.s.sol:ScriptExample","label":"counter","offset":0,"slot":"0","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}},"userdoc":{"version":1,"kind":"user","methods":{"hello(string)":{"notice":"example external function, to force a CALL, and test vm.startPrank with."},"run()":{"notice":"example function, runs through basic cheat-codes and console logs."},"runBroadcast()":{"notice":"example function, to test vm.broadcast with."}},"notice":"ScriptExample is an example script. The Go forge script code tests that it can run this."},"devdoc":{"version":1,"kind":"dev","title":"ScriptExample"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":720,"exportedSymbols":{"FooBar":[719],"ScriptExample":[706],"Vm":[55],"console":[192]},"nodeType":"SourceUnit","src":"32:5967:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":55,"nodeType":"ContractDefinition","src":"120:616:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":55,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[55],"name":"Vm","nameLocation":"130:2:0","scope":720,"usedErrors":[]},{"id":192,"nodeType":"ContractDefinition","src":"791:1622:0","nodes":[{"id":61,"nodeType":"VariableDeclaration","src":"813:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"830:15:0","scope":192,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":56,"name":"address","nodeType":"ElementaryTypeName","src":"813:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":59,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"856:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":58,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"848:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":57,"name":"address","nodeType":"ElementaryTypeName","src":"848:7:0","typeDescriptions":{}}},"id":60,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"848:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":78,"nodeType":"FunctionDefinition","src":"906:221:0","nodes":[],"body":{"id":77,"nodeType":"Block","src":"1065:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1084:37:0","statements":[{"nodeType":"YulAssignment","src":"1098:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1107:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1098:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":67,"isOffset":false,"isSlot":false,"src":"1107:4:0","valueSize":1},{"declaration":74,"isOffset":false,"isSlot":false,"src":"1098:5:0","valueSize":1}],"id":76,"nodeType":"InlineAssembly","src":"1075:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"915:25:0","parameters":{"id":68,"nodeType":"ParameterList","parameters":[{"constant":false,"id":67,"mutability":"mutable","name":"fnIn","nameLocation":"987:4:0","nodeType":"VariableDeclaration","scope":78,"src":"950:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":66,"nodeType":"FunctionTypeName","parameterTypes":{"id":64,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":66,"src":"959:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":62,"name":"bytes","nodeType":"ElementaryTypeName","src":"959:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"958:14:0"},"returnParameterTypes":{"id":65,"nodeType":"ParameterList","parameters":[],"src":"987:0:0"},"src":"950:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"940:57:0"},"returnParameters":{"id":75,"nodeType":"ParameterList","parameters":[{"constant":false,"id":74,"mutability":"mutable","name":"fnOut","nameLocation":"1058:5:0","nodeType":"VariableDeclaration","scope":78,"src":"1021:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":73,"nodeType":"FunctionTypeName","parameterTypes":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":73,"src":"1030:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":69,"name":"bytes","nodeType":"ElementaryTypeName","src":"1030:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1029:14:0"},"returnParameterTypes":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"1058:0:0"},"src":"1021:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1020:44:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":90,"nodeType":"FunctionDefinition","src":"1133:133:0","nodes":[],"body":{"id":89,"nodeType":"Block","src":"1194:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":86,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":80,"src":"1251:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":84,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1230:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":83,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":78,"src":"1204:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":85,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":87,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":88,"nodeType":"ExpressionStatement","src":"1204:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1142:15:0","parameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"payload","nameLocation":"1171:7:0","nodeType":"VariableDeclaration","scope":90,"src":"1158:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":79,"name":"bytes","nodeType":"ElementaryTypeName","src":"1158:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1157:22:0"},"returnParameters":{"id":82,"nodeType":"ParameterList","parameters":[],"src":"1194:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1272:380:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1336:316:0","nodes":[],"statements":[{"assignments":[96],"declarations":[{"constant":false,"id":96,"mutability":"mutable","name":"payloadLength","nameLocation":"1354:13:0","nodeType":"VariableDeclaration","scope":105,"src":"1346:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":95,"name":"uint256","nodeType":"ElementaryTypeName","src":"1346:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":99,"initialValue":{"expression":{"id":97,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":92,"src":"1370:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":98,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1370:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1346:38:0"},{"assignments":[101],"declarations":[{"constant":false,"id":101,"mutability":"mutable","name":"consoleAddress","nameLocation":"1402:14:0","nodeType":"VariableDeclaration","scope":105,"src":"1394:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":100,"name":"address","nodeType":"ElementaryTypeName","src":"1394:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":103,"initialValue":{"id":102,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":61,"src":"1419:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1394:40:0"},{"AST":{"nodeType":"YulBlock","src":"1496:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1510:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1534:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1543:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1530:3:0"},"nodeType":"YulFunctionCall","src":"1530:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1514:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1559:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1579:3:0"},"nodeType":"YulFunctionCall","src":"1579:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1586:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1602:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1616:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1631:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"1634:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1568:10:0"},"nodeType":"YulFunctionCall","src":"1568:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1563:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":101,"isOffset":false,"isSlot":false,"src":"1586:14:0","valueSize":1},{"declaration":92,"isOffset":false,"isSlot":false,"src":"1534:7:0","valueSize":1},{"declaration":96,"isOffset":false,"isSlot":false,"src":"1616:13:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1487:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1281:19:0","parameters":{"id":93,"nodeType":"ParameterList","parameters":[{"constant":false,"id":92,"mutability":"mutable","name":"payload","nameLocation":"1314:7:0","nodeType":"VariableDeclaration","scope":106,"src":"1301:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":91,"name":"bytes","nodeType":"ElementaryTypeName","src":"1301:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1300:22:0"},"returnParameters":{"id":94,"nodeType":"ParameterList","parameters":[],"src":"1336:0:0"},"scope":192,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":120,"nodeType":"FunctionDefinition","src":"1658:121:0","nodes":[],"body":{"id":119,"nodeType":"Block","src":"1703:76:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":114,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1753:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":115,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1768:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":112,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1729:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":113,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1729: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":116,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1729:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":111,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1713:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":117,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1713:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":118,"nodeType":"ExpressionStatement","src":"1713:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1667:3:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"p0","nameLocation":"1685:2:0","nodeType":"VariableDeclaration","scope":120,"src":"1671:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":107,"name":"string","nodeType":"ElementaryTypeName","src":"1671:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"1670:18:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":137,"nodeType":"FunctionDefinition","src":"1785:139:0","nodes":[],"body":{"id":136,"nodeType":"Block","src":"1839:85:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":130,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1889:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":131,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":122,"src":"1909:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":132,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":124,"src":"1913:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":128,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1865:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":129,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1865: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":133,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1865:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":127,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1849:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":134,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1849:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":135,"nodeType":"ExpressionStatement","src":"1849:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1794:3:0","parameters":{"id":125,"nodeType":"ParameterList","parameters":[{"constant":false,"id":122,"mutability":"mutable","name":"p0","nameLocation":"1812:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1798:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":121,"name":"string","nodeType":"ElementaryTypeName","src":"1798:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":124,"mutability":"mutable","name":"p1","nameLocation":"1821:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1816:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":123,"name":"bool","nodeType":"ElementaryTypeName","src":"1816:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"1797:27:0"},"returnParameters":{"id":126,"nodeType":"ParameterList","parameters":[],"src":"1839:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":154,"nodeType":"FunctionDefinition","src":"1930:145:0","nodes":[],"body":{"id":153,"nodeType":"Block","src":"1987:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":147,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2037:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":148,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":139,"src":"2060:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":149,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":141,"src":"2064:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":145,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2013:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":146,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2013: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":150,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2013:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":144,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1997:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":151,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1997:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":152,"nodeType":"ExpressionStatement","src":"1997:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1939:3:0","parameters":{"id":142,"nodeType":"ParameterList","parameters":[{"constant":false,"id":139,"mutability":"mutable","name":"p0","nameLocation":"1957:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1943:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":138,"name":"string","nodeType":"ElementaryTypeName","src":"1943:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":141,"mutability":"mutable","name":"p1","nameLocation":"1969:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1961:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":140,"name":"uint256","nodeType":"ElementaryTypeName","src":"1961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1942:30:0"},"returnParameters":{"id":143,"nodeType":"ParameterList","parameters":[],"src":"1987:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":171,"nodeType":"FunctionDefinition","src":"2081:145:0","nodes":[],"body":{"id":170,"nodeType":"Block","src":"2138:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":164,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2188:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":165,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":156,"src":"2211:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":166,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":158,"src":"2215:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":162,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2164:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":163,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2164: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":167,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2164:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":161,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2148:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":168,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2148:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":169,"nodeType":"ExpressionStatement","src":"2148:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2090:3:0","parameters":{"id":159,"nodeType":"ParameterList","parameters":[{"constant":false,"id":156,"mutability":"mutable","name":"p0","nameLocation":"2108:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2094:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":155,"name":"string","nodeType":"ElementaryTypeName","src":"2094:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":158,"mutability":"mutable","name":"p1","nameLocation":"2120:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2112:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":157,"name":"address","nodeType":"ElementaryTypeName","src":"2112:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2093:30:0"},"returnParameters":{"id":160,"nodeType":"ParameterList","parameters":[],"src":"2138:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":191,"nodeType":"FunctionDefinition","src":"2232:179:0","nodes":[],"body":{"id":190,"nodeType":"Block","src":"2313:98:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":183,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2363:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":184,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":173,"src":"2392:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":185,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":175,"src":"2396:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":186,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":177,"src":"2400:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":181,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2339:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":182,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2339: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":187,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2339:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":180,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2323:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":188,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2323:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":189,"nodeType":"ExpressionStatement","src":"2323:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2241:3:0","parameters":{"id":178,"nodeType":"ParameterList","parameters":[{"constant":false,"id":173,"mutability":"mutable","name":"p0","nameLocation":"2259:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2245:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":172,"name":"string","nodeType":"ElementaryTypeName","src":"2245:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":175,"mutability":"mutable","name":"p1","nameLocation":"2277:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2263:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":174,"name":"string","nodeType":"ElementaryTypeName","src":"2263:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":177,"mutability":"mutable","name":"p2","nameLocation":"2295:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2281:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":176,"name":"string","nodeType":"ElementaryTypeName","src":"2281:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2244:54:0"},"returnParameters":{"id":179,"nodeType":"ParameterList","parameters":[],"src":"2313:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[192],"name":"console","nameLocation":"799:7:0","scope":720,"usedErrors":[]},{"id":706,"nodeType":"ContractDefinition","src":"2541:3359:0","nodes":[{"id":207,"nodeType":"VariableDeclaration","src":"2571:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"2597:10:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":194,"name":"address","nodeType":"ElementaryTypeName","src":"2571:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":202,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2644:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":201,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"2634:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":203,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2634:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":200,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2626:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":199,"name":"uint256","nodeType":"ElementaryTypeName","src":"2626:7:0","typeDescriptions":{}}},"id":204,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2626:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":198,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2618:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":197,"name":"uint160","nodeType":"ElementaryTypeName","src":"2618:7:0","typeDescriptions":{}}},"id":205,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2618:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":196,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2610:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":195,"name":"address","nodeType":"ElementaryTypeName","src":"2610:7:0","typeDescriptions":{}}},"id":206,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2610:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":213,"nodeType":"VariableDeclaration","src":"2671:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"2692:2:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"},"typeName":{"id":209,"nodeType":"UserDefinedTypeName","pathNode":{"id":208,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":55,"src":"2671:2:0"},"referencedDeclaration":55,"src":"2671:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"value":{"arguments":[{"id":211,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":207,"src":"2700:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":210,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":55,"src":"2697:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$55_$","typeString":"type(contract Vm)"}},"id":212,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2697:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"visibility":"internal"},{"id":215,"nodeType":"VariableDeclaration","src":"2775:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"2790:7:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":214,"name":"uint256","nodeType":"ElementaryTypeName","src":"2775:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":378,"nodeType":"FunctionDefinition","src":"2887:949:0","nodes":[],"body":{"id":377,"nodeType":"Block","src":"2909:927:0","nodes":[],"statements":[{"assignments":[220],"declarations":[{"constant":false,"id":220,"mutability":"mutable","name":"x","nameLocation":"2924:1:0","nodeType":"VariableDeclaration","scope":377,"src":"2919:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":219,"name":"bool","nodeType":"ElementaryTypeName","src":"2919:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":226,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":223,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2937:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":224,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"2953:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":221,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"2928:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":222,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"2928:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":225,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2928:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"2919:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2981:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":231,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3004:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":227,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"2969:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":229,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":137,"src":"2969:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":232,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2969:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":233,"nodeType":"ExpressionStatement","src":"2969:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":237,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3029:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":240,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3054:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":239,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3046:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":238,"name":"address","nodeType":"ElementaryTypeName","src":"3046:7:0","typeDescriptions":{}}},"id":241,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3046:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":234,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3017:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":236,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3017:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":242,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3017:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":243,"nodeType":"ExpressionStatement","src":"3017:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":247,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3082:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":252,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3120:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":251,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3112:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":250,"name":"address","nodeType":"ElementaryTypeName","src":"3112:7:0","typeDescriptions":{}}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3112:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":248,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3100:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":249,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3100:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":254,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3100:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":244,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3070:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":246,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3070:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":255,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3070:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":256,"nodeType":"ExpressionStatement","src":"3070:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":260,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3149:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":263,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3172:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3172:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":262,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3164:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":261,"name":"address","nodeType":"ElementaryTypeName","src":"3164:7:0","typeDescriptions":{}}},"id":265,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3164:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":257,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3137:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":259,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3137:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":266,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3137:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":267,"nodeType":"ExpressionStatement","src":"3137:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":271,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3206:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":276,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3242:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3242:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":275,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3234:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":274,"name":"address","nodeType":"ElementaryTypeName","src":"3234:7:0","typeDescriptions":{}}},"id":278,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3234:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":272,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3222:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":273,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3222:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":279,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3222:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":268,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3194:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3194:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":280,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3194:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":281,"nodeType":"ExpressionStatement","src":"3194:61:0"},{"assignments":[283],"declarations":[{"constant":false,"id":283,"mutability":"mutable","name":"json","nameLocation":"3280:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3266:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":282,"name":"string","nodeType":"ElementaryTypeName","src":"3266:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":285,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":284,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3287:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3266:55:0"},{"assignments":[290],"declarations":[{"constant":false,"id":290,"mutability":"mutable","name":"keys","nameLocation":"3347:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3331:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":288,"name":"string","nodeType":"ElementaryTypeName","src":"3331:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":289,"nodeType":"ArrayTypeName","src":"3331:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":296,"initialValue":{"arguments":[{"id":293,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":283,"src":"3371:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":294,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3377:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":291,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3354:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3354:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":295,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3354:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3331:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":300,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3414:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":301,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3422:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":303,"indexExpression":{"hexValue":"30","id":302,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3427:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3422:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":304,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3431:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":306,"indexExpression":{"hexValue":"31","id":305,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3436:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3431:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":297,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3402:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":299,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":191,"src":"3402:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3402:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":308,"nodeType":"ExpressionStatement","src":"3402:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3461:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":309,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3450:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":311,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3450:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":313,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3450:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":314,"nodeType":"ExpressionStatement","src":"3450:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3517:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":321,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3509:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":320,"name":"uint160","nodeType":"ElementaryTypeName","src":"3509:7:0","typeDescriptions":{}}},"id":323,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":319,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3501:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":318,"name":"address","nodeType":"ElementaryTypeName","src":"3501:7:0","typeDescriptions":{}}},"id":324,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3501:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":315,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3487:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":317,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"3487:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":325,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3487:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":326,"nodeType":"ExpressionStatement","src":"3487:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3545:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":327,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3534:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":329,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3534:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":331,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3534:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":332,"nodeType":"ExpressionStatement","src":"3534:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":336,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3582:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":339,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3617:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":340,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3617:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":338,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3609:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":337,"name":"address","nodeType":"ElementaryTypeName","src":"3609:7:0","typeDescriptions":{}}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3609:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":333,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3570:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3570:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":342,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3570:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":343,"nodeType":"ExpressionStatement","src":"3570:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":347,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3651:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":350,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3689:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3681:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":348,"name":"address","nodeType":"ElementaryTypeName","src":"3681:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":344,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3639:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":346,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3639:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":352,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3639:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":353,"nodeType":"ExpressionStatement","src":"3639:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":357,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3716:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":354,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3705:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":356,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3705:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":358,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3705:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":359,"nodeType":"ExpressionStatement","src":"3705:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":360,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3741:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":362,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"3741:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3741:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":364,"nodeType":"ExpressionStatement","src":"3741:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":368,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3776:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":365,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3765:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":367,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3765:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3765:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":370,"nodeType":"ExpressionStatement","src":"3765:33:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":374,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3821:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":371,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3809:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":373,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"3809:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":375,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3809:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":376,"nodeType":"ExpressionStatement","src":"3809:20:0"}]},"documentation":{"id":216,"nodeType":"StructuredDocumentation","src":"2804:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"2896:3:0","parameters":{"id":217,"nodeType":"ParameterList","parameters":[],"src":"2899:2:0"},"returnParameters":{"id":218,"nodeType":"ParameterList","parameters":[],"src":"2909:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":609,"nodeType":"FunctionDefinition","src":"3903:1258:0","nodes":[],"body":{"id":608,"nodeType":"Block","src":"3934:1227:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3956:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":392,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3999:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":391,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3991:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":390,"name":"address","nodeType":"ElementaryTypeName","src":"3991:7:0","typeDescriptions":{}}},"id":393,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3991:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3979:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":389,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3979:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":394,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3979:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":387,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3971:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":386,"name":"uint256","nodeType":"ElementaryTypeName","src":"3971:7:0","typeDescriptions":{}}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3971:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":382,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3944:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3944:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":396,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3944:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":397,"nodeType":"ExpressionStatement","src":"3944:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":401,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4030:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":398,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4018:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":400,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4018:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":402,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4018:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":403,"nodeType":"ExpressionStatement","src":"4018:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":404,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4057:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":406,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"4057:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":407,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4057:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":408,"nodeType":"ExpressionStatement","src":"4057:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4092:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":409,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4081:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":411,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4081:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":413,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4081:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":414,"nodeType":"ExpressionStatement","src":"4081:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":418,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4128:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":415,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4117:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":417,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4117:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4117:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":420,"nodeType":"ExpressionStatement","src":"4117:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":424,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4166:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":421,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4154:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":423,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4154:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":425,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4154:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":426,"nodeType":"ExpressionStatement","src":"4154:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":434,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4231:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":433,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4223:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":432,"name":"uint160","nodeType":"ElementaryTypeName","src":"4223:7:0","typeDescriptions":{}}},"id":435,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4223:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":431,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4215:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":430,"name":"address","nodeType":"ElementaryTypeName","src":"4215:7:0","typeDescriptions":{}}},"id":436,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4215:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":427,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4197:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":429,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4197:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":437,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4197:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":438,"nodeType":"ExpressionStatement","src":"4197:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":442,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4263:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":439,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4252:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":441,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4252:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":443,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4252:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":444,"nodeType":"ExpressionStatement","src":"4252:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":448,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4302:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":445,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4291:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4291:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":449,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4291:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":450,"nodeType":"ExpressionStatement","src":"4291:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":454,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4344:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":451,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4330:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":705,"src":"4330:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":455,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4330:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":456,"nodeType":"ExpressionStatement","src":"4330:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":457,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4371:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":459,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4371:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":460,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4371:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":461,"nodeType":"ExpressionStatement","src":"4371:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":465,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4410:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":462,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4399:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":464,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4399:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":466,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4399:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":467,"nodeType":"ExpressionStatement","src":"4399:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":471,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4451:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":468,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4439:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":470,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4439:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4439:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":473,"nodeType":"ExpressionStatement","src":"4439:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":481,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4512:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":480,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4504:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":479,"name":"uint160","nodeType":"ElementaryTypeName","src":"4504:7:0","typeDescriptions":{}}},"id":482,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4504:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":478,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4496:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":477,"name":"address","nodeType":"ElementaryTypeName","src":"4496:7:0","typeDescriptions":{}}},"id":483,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4496:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":474,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4478:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":476,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4478:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4478:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":485,"nodeType":"ExpressionStatement","src":"4478:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":489,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4544:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":486,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4531:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":488,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":678,"src":"4531:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":490,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4531:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":491,"nodeType":"ExpressionStatement","src":"4531:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":492,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4563:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":494,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4563:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4563:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":496,"nodeType":"ExpressionStatement","src":"4563:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":500,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4604:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":497,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4592:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":499,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4592:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4592:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":502,"nodeType":"ExpressionStatement","src":"4592:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":510,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4665:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4657:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":508,"name":"uint160","nodeType":"ElementaryTypeName","src":"4657:7:0","typeDescriptions":{}}},"id":511,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":507,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4649:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":506,"name":"address","nodeType":"ElementaryTypeName","src":"4649:7:0","typeDescriptions":{}}},"id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4649:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":503,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4636:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":505,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4636:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":513,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4636:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":514,"nodeType":"ExpressionStatement","src":"4636:40:0"},{"assignments":[517],"declarations":[{"constant":false,"id":517,"mutability":"mutable","name":"x","nameLocation":"4693:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4686:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":516,"nodeType":"UserDefinedTypeName","pathNode":{"id":515,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4686:6:0"},"referencedDeclaration":719,"src":"4686:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":523,"initialValue":{"arguments":[{"hexValue":"31323334","id":521,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4708:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":520,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4697:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":519,"nodeType":"UserDefinedTypeName","pathNode":{"id":518,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4701:6:0"},"referencedDeclaration":719,"src":"4701:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":522,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4697:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4686:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":529,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":525,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":517,"src":"4731:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":526,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4731:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4731:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":528,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4742:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4731:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":524,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4723:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":530,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4723:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":531,"nodeType":"ExpressionStatement","src":"4723:24:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":535,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4770:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":532,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4758:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":534,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4758:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":536,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4758:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":537,"nodeType":"ExpressionStatement","src":"4758:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":545,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4820:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":544,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4812:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":543,"name":"uint160","nodeType":"ElementaryTypeName","src":"4812:7:0","typeDescriptions":{}}},"id":546,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4812:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":542,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4804:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":541,"name":"address","nodeType":"ElementaryTypeName","src":"4804:7:0","typeDescriptions":{}}},"id":547,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4804:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":538,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4791:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":540,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4791:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4791:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":549,"nodeType":"ExpressionStatement","src":"4791:38:0"},{"assignments":[552],"declarations":[{"constant":false,"id":552,"mutability":"mutable","name":"y","nameLocation":"4846:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4839:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":551,"nodeType":"UserDefinedTypeName","pathNode":{"id":550,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4839:6:0"},"referencedDeclaration":719,"src":"4839:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":566,"initialValue":{"arguments":[{"hexValue":"31323334","id":564,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4889:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":555,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4850:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":554,"nodeType":"UserDefinedTypeName","pathNode":{"id":553,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4854:6:0"},"referencedDeclaration":719,"src":"4854:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":563,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4883:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":559,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4875:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":558,"name":"uint256","nodeType":"ElementaryTypeName","src":"4875:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4875:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":557,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4867:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":556,"name":"bytes32","nodeType":"ElementaryTypeName","src":"4867:7:0","typeDescriptions":{}}},"id":562,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4867:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"4850:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":565,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4850:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4839:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":568,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":552,"src":"4912:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":569,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4912:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":570,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4912:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":571,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4923:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4912:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":567,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4904:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4904:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"4904:24:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4950:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4938:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4938:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4938:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"4938:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5042:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5042:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":584,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5042:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":585,"nodeType":"ExpressionStatement","src":"5042:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5077:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":588,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5066:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":587,"nodeType":"UserDefinedTypeName","pathNode":{"id":586,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"5070:6:0"},"referencedDeclaration":719,"src":"5070:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":590,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5066:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":591,"nodeType":"ExpressionStatement","src":"5066:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":595,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5105:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":602,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5146:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":601,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5138:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":600,"name":"address","nodeType":"ElementaryTypeName","src":"5138:7:0","typeDescriptions":{}}},"id":603,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5138:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":598,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5126:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":599,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5126:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5126:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":597,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5118:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":596,"name":"uint256","nodeType":"ElementaryTypeName","src":"5118:7:0","typeDescriptions":{}}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5118:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":592,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5093:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":594,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"5093:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":606,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5093:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":607,"nodeType":"ExpressionStatement","src":"5093:61:0"}]},"documentation":{"id":379,"nodeType":"StructuredDocumentation","src":"3842:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"3912:12:0","parameters":{"id":380,"nodeType":"ParameterList","parameters":[],"src":"3924:2:0"},"returnParameters":{"id":381,"nodeType":"ParameterList","parameters":[],"src":"3934:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":633,"nodeType":"FunctionDefinition","src":"5256:143:0","nodes":[],"body":{"id":632,"nodeType":"Block","src":"5305:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":618,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":612,"src":"5327:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":615,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5315:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":617,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5315:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5315:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":620,"nodeType":"ExpressionStatement","src":"5315:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5352:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":627,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"5380:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":628,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"5380:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":626,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5372:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":625,"name":"address","nodeType":"ElementaryTypeName","src":"5372:7:0","typeDescriptions":{}}},"id":629,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5372:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":621,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5340:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":623,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"5340:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":630,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5340:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":631,"nodeType":"ExpressionStatement","src":"5340:52:0"}]},"documentation":{"id":610,"nodeType":"StructuredDocumentation","src":"5167:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"5265:5:0","parameters":{"id":613,"nodeType":"ParameterList","parameters":[{"constant":false,"id":612,"mutability":"mutable","name":"_v","nameLocation":"5287:2:0","nodeType":"VariableDeclaration","scope":633,"src":"5271:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":611,"name":"string","nodeType":"ElementaryTypeName","src":"5271:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5270:20:0"},"returnParameters":{"id":614,"nodeType":"ParameterList","parameters":[],"src":"5305:0:0"},"scope":706,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":648,"nodeType":"FunctionDefinition","src":"5405:95:0","nodes":[],"body":{"id":647,"nodeType":"Block","src":"5449:51:0","nodes":[],"statements":[{"expression":{"id":639,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5459:9:0","subExpression":{"id":638,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5459:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":640,"nodeType":"ExpressionStatement","src":"5459:9:0"},{"expression":{"arguments":[{"id":644,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":635,"src":"5490:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":641,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5478:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":643,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5478:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":645,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5478:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":646,"nodeType":"ExpressionStatement","src":"5478:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"5414:5:0","parameters":{"id":636,"nodeType":"ParameterList","parameters":[{"constant":false,"id":635,"mutability":"mutable","name":"_v","nameLocation":"5436:2:0","nodeType":"VariableDeclaration","scope":648,"src":"5420:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":634,"name":"string","nodeType":"ElementaryTypeName","src":"5420:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5419:20:0"},"returnParameters":{"id":637,"nodeType":"ParameterList","parameters":[],"src":"5449:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":663,"nodeType":"FunctionDefinition","src":"5506:95:0","nodes":[],"body":{"id":662,"nodeType":"Block","src":"5550:51:0","nodes":[],"statements":[{"expression":{"id":654,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5560:9:0","subExpression":{"id":653,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5560:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":655,"nodeType":"ExpressionStatement","src":"5560:9:0"},{"expression":{"arguments":[{"id":659,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":650,"src":"5591:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":656,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5579:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":658,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5579:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":660,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5579:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":661,"nodeType":"ExpressionStatement","src":"5579:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"5515:5:0","parameters":{"id":651,"nodeType":"ParameterList","parameters":[{"constant":false,"id":650,"mutability":"mutable","name":"_v","nameLocation":"5537:2:0","nodeType":"VariableDeclaration","scope":663,"src":"5521:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":649,"name":"string","nodeType":"ElementaryTypeName","src":"5521:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5520:20:0"},"returnParameters":{"id":652,"nodeType":"ParameterList","parameters":[],"src":"5550:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":678,"nodeType":"FunctionDefinition","src":"5607:98:0","nodes":[],"body":{"id":677,"nodeType":"Block","src":"5653:52:0","nodes":[],"statements":[{"expression":{"id":669,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5663:9:0","subExpression":{"id":668,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5663:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":670,"nodeType":"ExpressionStatement","src":"5663:9:0"},{"expression":{"arguments":[{"id":674,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":665,"src":"5695:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":671,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5682:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":673,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":693,"src":"5682:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":675,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5682:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":676,"nodeType":"ExpressionStatement","src":"5682:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"5616:7:0","parameters":{"id":666,"nodeType":"ParameterList","parameters":[{"constant":false,"id":665,"mutability":"mutable","name":"_v","nameLocation":"5640:2:0","nodeType":"VariableDeclaration","scope":678,"src":"5624:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":664,"name":"string","nodeType":"ElementaryTypeName","src":"5624:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5623:20:0"},"returnParameters":{"id":667,"nodeType":"ParameterList","parameters":[],"src":"5653:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":693,"nodeType":"FunctionDefinition","src":"5711:97:0","nodes":[],"body":{"id":692,"nodeType":"Block","src":"5757:51:0","nodes":[],"statements":[{"expression":{"id":684,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5767:9:0","subExpression":{"id":683,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5767:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":685,"nodeType":"ExpressionStatement","src":"5767:9:0"},{"expression":{"arguments":[{"id":689,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":680,"src":"5798:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":686,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5786:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":688,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5786:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":690,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5786:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":691,"nodeType":"ExpressionStatement","src":"5786:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"5720:7:0","parameters":{"id":681,"nodeType":"ParameterList","parameters":[{"constant":false,"id":680,"mutability":"mutable","name":"_v","nameLocation":"5744:2:0","nodeType":"VariableDeclaration","scope":693,"src":"5728:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":679,"name":"string","nodeType":"ElementaryTypeName","src":"5728:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5727:20:0"},"returnParameters":{"id":682,"nodeType":"ParameterList","parameters":[],"src":"5757:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":705,"nodeType":"FunctionDefinition","src":"5814:84:0","nodes":[],"body":{"id":704,"nodeType":"Block","src":"5866:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":701,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":695,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":698,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5876:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":700,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5876:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":702,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5876:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":703,"nodeType":"ExpressionStatement","src":"5876:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"5823:8:0","parameters":{"id":696,"nodeType":"ParameterList","parameters":[{"constant":false,"id":695,"mutability":"mutable","name":"_v","nameLocation":"5848:2:0","nodeType":"VariableDeclaration","scope":705,"src":"5832:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":694,"name":"string","nodeType":"ElementaryTypeName","src":"5832:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5831:20:0"},"returnParameters":{"id":697,"nodeType":"ParameterList","parameters":[],"src":"5866:0:0"},"scope":706,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[719],"contractKind":"contract","documentation":{"id":193,"nodeType":"StructuredDocumentation","src":"2415:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[706],"name":"ScriptExample","nameLocation":"2550:13:0","scope":720,"usedErrors":[]},{"id":719,"nodeType":"ContractDefinition","src":"5902:96:0","nodes":[{"id":708,"nodeType":"VariableDeclaration","src":"5924:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"5939:3:0","scope":719,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":707,"name":"uint256","nodeType":"ElementaryTypeName","src":"5924:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":718,"nodeType":"FunctionDefinition","src":"5949:47:0","nodes":[],"body":{"id":717,"nodeType":"Block","src":"5972:24:0","nodes":[],"statements":[{"expression":{"id":715,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":713,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":708,"src":"5982:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":714,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":710,"src":"5988:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"5982:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":716,"nodeType":"ExpressionStatement","src":"5982:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":711,"nodeType":"ParameterList","parameters":[{"constant":false,"id":710,"mutability":"mutable","name":"v","nameLocation":"5969:1:0","nodeType":"VariableDeclaration","scope":718,"src":"5961:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":709,"name":"uint256","nodeType":"ElementaryTypeName","src":"5961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"5960:11:0"},"returnParameters":{"id":712,"nodeType":"ParameterList","parameters":[],"src":"5972:0:0"},"scope":719,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[719],"name":"FooBar","nameLocation":"5911:6:0","scope":720,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file +{"abi":[{"type":"function","name":"call1","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"call2","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"callPure","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"pure"},{"type":"function","name":"counter","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"hello","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"view"},{"type":"function","name":"nested1","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"nested2","inputs":[{"name":"_v","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"run","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"runBroadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b506124b2806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c8063a76ccdfa11610076578063bef03abc1161005b578063bef03abc14610111578063c040622614610119578063dbf1282f146100c357600080fd5b8063a76ccdfa146100eb578063a777d0dc146100fe57600080fd5b806361bc221a146100a85780637e79255d146100c35780637f8b915c146100d85780638d3ef7ca146100c3575b600080fd5b6100b160005481565b60405190815260200160405180910390f35b6100d66100d1366004611e15565b610121565b005b6100d66100e6366004611e15565b610178565b6100d66100f9366004611e15565b6101b7565b6100d661010c366004611e15565b61023f565b6100d66102bd565b6100d661105f565b60008054908061013083611e87565b919050555061017482828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611b0792505050565b5050565b61017482828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611b0792505050565b6000805490806101c683611e87565b90915550506040517fdbf1282f000000000000000000000000000000000000000000000000000000008152309063dbf1282f906102099085908590600401611ee6565b600060405180830381600087803b15801561022357600080fd5b505af1158015610237573d6000803e3d6000fd5b505050505050565b61027e82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611b0792505050565b6101746040518060400160405280601081526020017f68656c6c6f206d73672e73656e6465720000000000000000000000000000000081525033611b99565b604080518082018252600b81527f6e6f6e6365207374617274000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526103909190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab906024015b602060405180830381865afa15801561035d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103819190611f33565b67ffffffffffffffff16611c2a565b6103ce6040518060400160405280600e81526020017f74657374696e672073696e676c65000000000000000000000000000000000000815250611b07565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561043957600080fd5b505af115801561044d573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3100000000000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156104d057600080fd5b505af11580156104e4573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3200000000000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561056757600080fd5b505af115801561057b573d6000803e3d6000fd5b505050506105bd6040518060400160405280601281526020017f74657374696e672073746172742f73746f700000000000000000000000000000815250611b07565b6040517f7fec2a8d00000000000000000000000000000000000000000000000000000000815262c0ffee6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b15801561062557600080fd5b505af1158015610639573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3100000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156106bc57600080fd5b505af11580156106d0573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3200000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561075357600080fd5b505af1158015610767573d6000803e3d6000fd5b50506040517f7f8b915c00000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f737461727473746f705f707572650000000000000000000000000000000000006044820152309250637f8b915c915060640160006040518083038186803b1580156107e857600080fd5b505afa1580156107fc573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561086b57600080fd5b505af115801561087f573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3300000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b15801561090257600080fd5b505af1158015610916573d6000803e3d6000fd5b505050506109586040518060400160405280600e81526020017f74657374696e67206e6573746564000000000000000000000000000000000000815250611b07565b6040517f7fec2a8d0000000000000000000000000000000000000000000000000000000081526112346004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b1580156109bf57600080fd5b505af11580156109d3573d6000803e3d6000fd5b50506040517fa76ccdfa00000000000000000000000000000000000000000000000000000000815260206004820152600660248201527f6e65737465640000000000000000000000000000000000000000000000000000604482015230925063a76ccdfa9150606401600060405180830381600087803b158015610a5657600080fd5b505af1158015610a6a573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610ad957600080fd5b505af1158015610aed573d6000803e3d6000fd5b50505050610b2f6040518060400160405280601381526020017f636f6e7472616374206465706c6f796d656e7400000000000000000000000000815250611b07565b6040517fe6962cdb000000000000000000000000000000000000000000000000000000008152621234566004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610b9757600080fd5b505af1158015610bab573d6000803e3d6000fd5b5050505060006104d2604051610bc090611e09565b908152602001604051809103906000f080158015610be2573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c549190611f64565b6104d214610ce9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323360448201527f340000000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610d276040518060400160405280600881526020017f6372656174652032000000000000000000000000000000000000000000000000815250611b07565b6040517fe6962cdb00000000000000000000000000000000000000000000000000000000815261cafe6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610d8e57600080fd5b505af1158015610da2573d6000803e3d6000fd5b505050506000602a60001b6104d2604051610dbc90611e09565b9081526020018190604051809103906000f5905080158015610de2573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e549190611f64565b6104d214610ee4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f466f6f4261723a20666f6f20696e2063726561746532206973206e6f7420313260448201527f33340000000000000000000000000000000000000000000000000000000000006064820152608401610ce0565b610f226040518060400160405280600581526020017f646f6e6521000000000000000000000000000000000000000000000000000000815250611b07565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610f8d57600080fd5b505af1158015610fa1573d6000803e3d6000fd5b505050506104d2604051610fb490611e09565b908152602001604051809103906000f080158015610fd6573d6000803e3d6000fd5b5050604080518082018252600981527f6e6f6e636520656e640000000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526101749190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b604080517f4777f3cf0000000000000000000000000000000000000000000000000000000081526004810191909152600c60448201527f4558414d504c455f424f4f4c0000000000000000000000000000000000000000606482015260006024820181905290737109709ecfa91a80626ff3989d68f67f5b1dd12d90634777f3cf90608401602060405180830381865afa158015611101573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111259190611f7d565b90506111666040518060400160405280601381526020017f626f6f6c2076616c75652066726f6d20656e760000000000000000000000000081525082611cbb565b6111a56040518060400160405280600d81526020017f636f6e747261637420616464720000000000000000000000000000000000000081525030611b99565b604080518082018252600e81527f636f6e7472616374206e6f6e6365000000000000000000000000000000000000602082015290517f2d0335ab00000000000000000000000000000000000000000000000000000000815230600482015261122c9190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b61126b6040518060400160405280600b81526020017f73656e646572206164647200000000000000000000000000000000000000000081525033611b99565b604080518082018252600c81527f73656e646572206e6f6e63650000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523360048201526112f29190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b60408051808201825260208082527f7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d9082015290517f213e4198000000000000000000000000000000000000000000000000000000008152600090737109709ecfa91a80626ff3989d68f67f5b1dd12d9063213e419890611377908590600401612019565b600060405180830381865afa158015611394573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526113da9190810190612156565b90506114506040518060400160405280600481526020017f6b65797300000000000000000000000000000000000000000000000000000000815250826000815181106114285761142861222e565b6020026020010151836001815181106114435761144361222e565b6020026020010151611d4c565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f66726f6d206f726967696e616c000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156114cd57600080fd5b505afa1580156114e1573d6000803e3d6000fd5b50506040517f06447d5600000000000000000000000000000000000000000000000000000000815260426004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d92506306447d569150602401600060405180830381600087803b15801561154b57600080fd5b505af115801561155f573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b20310000000000000000000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b1580156115e057600080fd5b505afa1580156115f4573d6000803e3d6000fd5b505050506116376040518060400160405280601781526020017f706172656e742073636f7065206d73672e73656e64657200000000000000000081525033611b99565b6116766040518060400160405280601a81526020017f706172656e742073636f706520636f6e74726163742e6164647200000000000081525030611b99565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b203200000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156116f357600080fd5b505afa158015611707573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166390c5013b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561177657600080fd5b505af115801561178a573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f66726f6d206f726967696e616c20616761696e00000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b15801561180b57600080fd5b505afa15801561181f573d6000803e3d6000fd5b50506040517f3ebf73b400000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5363726970744578616d706c652e732e736f6c3a4e6f6e63654765747465720060448201527f12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a2529250737109709ecfa91a80626ff3989d68f67f5b1dd12d915063b4d6c7829083908390633ebf73b490606401600060405180830381865afa1580156118e5573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261192b919081019061225d565b6040518363ffffffff1660e01b81526004016119489291906122ae565b600060405180830381600087803b15801561196257600080fd5b505af1158015611976573d6000803e3d6000fd5b50506040517fea06029100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d925063ea0602919150602401600060405180830381600087803b1580156119f557600080fd5b505af1158015611a09573d6000803e3d6000fd5b50506040517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526000925073ffffffffffffffffffffffffffffffffffffffff84169150632d0335ab90602401602060405180830381865afa158015611a7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a9e9190611f64565b9050611ac26040518060800160405280604281526020016124646042913982611c2a565b611b006040518060400160405280600581526020017f646f6e6521000000000000000000000000000000000000000000000000000000815250611b07565b5050505050565b611b9681604051602401611b1b91906122dd565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f41304fac00000000000000000000000000000000000000000000000000000000179052611de4565b50565b6101748282604051602401611baf9291906122f0565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f319af33300000000000000000000000000000000000000000000000000000000179052611de4565b6101748282604051602401611c40929190612328565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fb60e72cc00000000000000000000000000000000000000000000000000000000179052611de4565b6101748282604051602401611cd192919061234a565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc3b5563500000000000000000000000000000000000000000000000000000000179052611de4565b611ddf838383604051602401611d649392919061236e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f2ced7cef00000000000000000000000000000000000000000000000000000000179052611de4565b505050565b611b968180516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60b2806123b283390190565b60008060208385031215611e2857600080fd5b823567ffffffffffffffff80821115611e4057600080fd5b818501915085601f830112611e5457600080fd5b813581811115611e6357600080fd5b866020828501011115611e7557600080fd5b60209290920196919550909350505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611edf577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b600060208284031215611f4557600080fd5b815167ffffffffffffffff81168114611f5d57600080fd5b9392505050565b600060208284031215611f7657600080fd5b5051919050565b600060208284031215611f8f57600080fd5b81518015158114611f5d57600080fd5b60005b83811015611fba578181015183820152602001611fa2565b83811115611fc9576000848401525b50505050565b60008151808452611fe7816020860160208601611f9f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60408152600061202c6040830184611fcf565b8281036020840152600c81527f2e726f6f745f6b65795b305d000000000000000000000000000000000000000060208201526040810191505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156120e1576120e161206b565b604052919050565b600067ffffffffffffffff8311156121035761210361206b565b61213460207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8601160161209a565b905082815283838301111561214857600080fd5b611f5d836020830184611f9f565b6000602080838503121561216957600080fd5b825167ffffffffffffffff8082111561218157600080fd5b818501915085601f83011261219557600080fd5b8151818111156121a7576121a761206b565b8060051b6121b685820161209a565b91825283810185019185810190898411156121d057600080fd5b86860192505b83831015612221578251858111156121ee5760008081fd5b8601603f81018b136122005760008081fd5b6122118b89830151604084016120e9565b83525091860191908601906121d6565b9998505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561226f57600080fd5b815167ffffffffffffffff81111561228657600080fd5b8201601f8101841361229757600080fd5b6122a6848251602084016120e9565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260006122a66040830184611fcf565b602081526000611f5d6020830184611fcf565b6040815260006123036040830185611fcf565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b60408152600061233b6040830185611fcf565b90508260208301529392505050565b60408152600061235d6040830185611fcf565b905082151560208301529392505050565b6060815260006123816060830186611fcf565b82810360208401526123938186611fcf565b905082810360408401526123a78185611fcf565b969550505050505056fe608060405234801561001057600080fd5b506040516100b23803806100b283398101604081905261002f91610037565b600055610050565b60006020828403121561004957600080fd5b5051919050565b60548061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000a6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683aa164736f6c634300080f000a","sourceMap":"3123:3912:0:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b50600436106100a35760003560e01c8063a76ccdfa11610076578063bef03abc1161005b578063bef03abc14610111578063c040622614610119578063dbf1282f146100c357600080fd5b8063a76ccdfa146100eb578063a777d0dc146100fe57600080fd5b806361bc221a146100a85780637e79255d146100c35780637f8b915c146100d85780638d3ef7ca146100c3575b600080fd5b6100b160005481565b60405190815260200160405180910390f35b6100d66100d1366004611e15565b610121565b005b6100d66100e6366004611e15565b610178565b6100d66100f9366004611e15565b6101b7565b6100d661010c366004611e15565b61023f565b6100d66102bd565b6100d661105f565b60008054908061013083611e87565b919050555061017482828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611b0792505050565b5050565b61017482828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611b0792505050565b6000805490806101c683611e87565b90915550506040517fdbf1282f000000000000000000000000000000000000000000000000000000008152309063dbf1282f906102099085908590600401611ee6565b600060405180830381600087803b15801561022357600080fd5b505af1158015610237573d6000803e3d6000fd5b505050505050565b61027e82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611b0792505050565b6101746040518060400160405280601081526020017f68656c6c6f206d73672e73656e6465720000000000000000000000000000000081525033611b99565b604080518082018252600b81527f6e6f6e6365207374617274000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526103909190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab906024015b602060405180830381865afa15801561035d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103819190611f33565b67ffffffffffffffff16611c2a565b6103ce6040518060400160405280600e81526020017f74657374696e672073696e676c65000000000000000000000000000000000000815250611b07565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561043957600080fd5b505af115801561044d573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3100000000000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156104d057600080fd5b505af11580156104e4573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f73696e676c655f63616c6c3200000000000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561056757600080fd5b505af115801561057b573d6000803e3d6000fd5b505050506105bd6040518060400160405280601281526020017f74657374696e672073746172742f73746f700000000000000000000000000000815250611b07565b6040517f7fec2a8d00000000000000000000000000000000000000000000000000000000815262c0ffee6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b15801561062557600080fd5b505af1158015610639573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3100000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b1580156106bc57600080fd5b505af11580156106d0573d6000803e3d6000fd5b50506040517f8d3ef7ca00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3200000000000000000000000000000000006044820152309250638d3ef7ca9150606401600060405180830381600087803b15801561075357600080fd5b505af1158015610767573d6000803e3d6000fd5b50506040517f7f8b915c00000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f737461727473746f705f707572650000000000000000000000000000000000006044820152309250637f8b915c915060640160006040518083038186803b1580156107e857600080fd5b505afa1580156107fc573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561086b57600080fd5b505af115801561087f573d6000803e3d6000fd5b50506040517f7e79255d00000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f737461727473746f705f63616c6c3300000000000000000000000000000000006044820152309250637e79255d9150606401600060405180830381600087803b15801561090257600080fd5b505af1158015610916573d6000803e3d6000fd5b505050506109586040518060400160405280600e81526020017f74657374696e67206e6573746564000000000000000000000000000000000000815250611b07565b6040517f7fec2a8d0000000000000000000000000000000000000000000000000000000081526112346004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d90637fec2a8d90602401600060405180830381600087803b1580156109bf57600080fd5b505af11580156109d3573d6000803e3d6000fd5b50506040517fa76ccdfa00000000000000000000000000000000000000000000000000000000815260206004820152600660248201527f6e65737465640000000000000000000000000000000000000000000000000000604482015230925063a76ccdfa9150606401600060405180830381600087803b158015610a5657600080fd5b505af1158015610a6a573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166376eadd366040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610ad957600080fd5b505af1158015610aed573d6000803e3d6000fd5b50505050610b2f6040518060400160405280601381526020017f636f6e7472616374206465706c6f796d656e7400000000000000000000000000815250611b07565b6040517fe6962cdb000000000000000000000000000000000000000000000000000000008152621234566004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610b9757600080fd5b505af1158015610bab573d6000803e3d6000fd5b5050505060006104d2604051610bc090611e09565b908152602001604051809103906000f080158015610be2573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c549190611f64565b6104d214610ce9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323360448201527f340000000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610d276040518060400160405280600881526020017f6372656174652032000000000000000000000000000000000000000000000000815250611b07565b6040517fe6962cdb00000000000000000000000000000000000000000000000000000000815261cafe6004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d9063e6962cdb90602401600060405180830381600087803b158015610d8e57600080fd5b505af1158015610da2573d6000803e3d6000fd5b505050506000602a60001b6104d2604051610dbc90611e09565b9081526020018190604051809103906000f5905080158015610de2573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e549190611f64565b6104d214610ee4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f466f6f4261723a20666f6f20696e2063726561746532206973206e6f7420313260448201527f33340000000000000000000000000000000000000000000000000000000000006064820152608401610ce0565b610f226040518060400160405280600581526020017f646f6e6521000000000000000000000000000000000000000000000000000000815250611b07565b7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff1663afc980406040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610f8d57600080fd5b505af1158015610fa1573d6000803e3d6000fd5b505050506104d2604051610fb490611e09565b908152602001604051809103906000f080158015610fd6573d6000803e3d6000fd5b5050604080518082018252600981527f6e6f6e636520656e640000000000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526101749190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b604080517f4777f3cf0000000000000000000000000000000000000000000000000000000081526004810191909152600c60448201527f4558414d504c455f424f4f4c0000000000000000000000000000000000000000606482015260006024820181905290737109709ecfa91a80626ff3989d68f67f5b1dd12d90634777f3cf90608401602060405180830381865afa158015611101573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111259190611f7d565b90506111666040518060400160405280601381526020017f626f6f6c2076616c75652066726f6d20656e760000000000000000000000000081525082611cbb565b6111a56040518060400160405280600d81526020017f636f6e747261637420616464720000000000000000000000000000000000000081525030611b99565b604080518082018252600e81527f636f6e7472616374206e6f6e6365000000000000000000000000000000000000602082015290517f2d0335ab00000000000000000000000000000000000000000000000000000000815230600482015261122c9190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b61126b6040518060400160405280600b81526020017f73656e646572206164647200000000000000000000000000000000000000000081525033611b99565b604080518082018252600c81527f73656e646572206e6f6e63650000000000000000000000000000000000000000602082015290517f2d0335ab0000000000000000000000000000000000000000000000000000000081523360048201526112f29190737109709ecfa91a80626ff3989d68f67f5b1dd12d90632d0335ab90602401610340565b60408051808201825260208082527f7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d9082015290517f213e4198000000000000000000000000000000000000000000000000000000008152600090737109709ecfa91a80626ff3989d68f67f5b1dd12d9063213e419890611377908590600401612019565b600060405180830381865afa158015611394573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526113da9190810190612156565b90506114506040518060400160405280600481526020017f6b65797300000000000000000000000000000000000000000000000000000000815250826000815181106114285761142861222e565b6020026020010151836001815181106114435761144361222e565b6020026020010151611d4c565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f66726f6d206f726967696e616c000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156114cd57600080fd5b505afa1580156114e1573d6000803e3d6000fd5b50506040517f06447d5600000000000000000000000000000000000000000000000000000000815260426004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d92506306447d569150602401600060405180830381600087803b15801561154b57600080fd5b505af115801561155f573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b20310000000000000000000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b1580156115e057600080fd5b505afa1580156115f4573d6000803e3d6000fd5b505050506116376040518060400160405280601781526020017f706172656e742073636f7065206d73672e73656e64657200000000000000000081525033611b99565b6116766040518060400160405280601a81526020017f706172656e742073636f706520636f6e74726163742e6164647200000000000081525030611b99565b6040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f66726f6d207072616e6b203200000000000000000000000000000000000000006044820152309063a777d0dc9060640160006040518083038186803b1580156116f357600080fd5b505afa158015611707573d6000803e3d6000fd5b505050507f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d60001c73ffffffffffffffffffffffffffffffffffffffff166390c5013b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561177657600080fd5b505af115801561178a573d6000803e3d6000fd5b50506040517fa777d0dc00000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f66726f6d206f726967696e616c20616761696e00000000000000000000000000604482015230925063a777d0dc915060640160006040518083038186803b15801561180b57600080fd5b505afa15801561181f573d6000803e3d6000fd5b50506040517f3ebf73b400000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5363726970744578616d706c652e732e736f6c3a4e6f6e63654765747465720060448201527f12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a2529250737109709ecfa91a80626ff3989d68f67f5b1dd12d915063b4d6c7829083908390633ebf73b490606401600060405180830381865afa1580156118e5573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261192b919081019061225d565b6040518363ffffffff1660e01b81526004016119489291906122ae565b600060405180830381600087803b15801561196257600080fd5b505af1158015611976573d6000803e3d6000fd5b50506040517fea06029100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152737109709ecfa91a80626ff3989d68f67f5b1dd12d925063ea0602919150602401600060405180830381600087803b1580156119f557600080fd5b505af1158015611a09573d6000803e3d6000fd5b50506040517f2d0335ab0000000000000000000000000000000000000000000000000000000081523060048201526000925073ffffffffffffffffffffffffffffffffffffffff84169150632d0335ab90602401602060405180830381865afa158015611a7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a9e9190611f64565b9050611ac26040518060800160405280604281526020016124646042913982611c2a565b611b006040518060400160405280600581526020017f646f6e6521000000000000000000000000000000000000000000000000000000815250611b07565b5050505050565b611b9681604051602401611b1b91906122dd565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f41304fac00000000000000000000000000000000000000000000000000000000179052611de4565b50565b6101748282604051602401611baf9291906122f0565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f319af33300000000000000000000000000000000000000000000000000000000179052611de4565b6101748282604051602401611c40929190612328565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fb60e72cc00000000000000000000000000000000000000000000000000000000179052611de4565b6101748282604051602401611cd192919061234a565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc3b5563500000000000000000000000000000000000000000000000000000000179052611de4565b611ddf838383604051602401611d649392919061236e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f2ced7cef00000000000000000000000000000000000000000000000000000000179052611de4565b505050565b611b968180516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60b2806123b283390190565b60008060208385031215611e2857600080fd5b823567ffffffffffffffff80821115611e4057600080fd5b818501915085601f830112611e5457600080fd5b813581811115611e6357600080fd5b866020828501011115611e7557600080fd5b60209290920196919550909350505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611edf577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b600060208284031215611f4557600080fd5b815167ffffffffffffffff81168114611f5d57600080fd5b9392505050565b600060208284031215611f7657600080fd5b5051919050565b600060208284031215611f8f57600080fd5b81518015158114611f5d57600080fd5b60005b83811015611fba578181015183820152602001611fa2565b83811115611fc9576000848401525b50505050565b60008151808452611fe7816020860160208601611f9f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60408152600061202c6040830184611fcf565b8281036020840152600c81527f2e726f6f745f6b65795b305d000000000000000000000000000000000000000060208201526040810191505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156120e1576120e161206b565b604052919050565b600067ffffffffffffffff8311156121035761210361206b565b61213460207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8601160161209a565b905082815283838301111561214857600080fd5b611f5d836020830184611f9f565b6000602080838503121561216957600080fd5b825167ffffffffffffffff8082111561218157600080fd5b818501915085601f83011261219557600080fd5b8151818111156121a7576121a761206b565b8060051b6121b685820161209a565b91825283810185019185810190898411156121d057600080fd5b86860192505b83831015612221578251858111156121ee5760008081fd5b8601603f81018b136122005760008081fd5b6122118b89830151604084016120e9565b83525091860191908601906121d6565b9998505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561226f57600080fd5b815167ffffffffffffffff81111561228657600080fd5b8201601f8101841361229757600080fd5b6122a6848251602084016120e9565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260006122a66040830184611fcf565b602081526000611f5d6020830184611fcf565b6040815260006123036040830185611fcf565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b60408152600061233b6040830185611fcf565b90508260208301529392505050565b60408152600061235d6040830185611fcf565b905082151560208301529392505050565b6060815260006123816060830186611fcf565b82810360208401526123938186611fcf565b905082810360408401526123a78185611fcf565b969550505050505056fe608060405234801561001057600080fd5b506040516100b23803806100b283398101604081905261002f91610037565b600055610050565b60006020828403121561004957600080fd5b5051919050565b60548061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b603560005481565b60405190815260200160405180910390f3fea164736f6c634300080f000a6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683aa164736f6c634300080f000a","sourceMap":"3123:3912:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3356:22;;;;;;;;;160:25:1;;;148:2;133:18;3356:22:0;;;;;;;6540:95;;;;;;:::i;:::-;;:::i;:::-;;6949:84;;;;;;:::i;:::-;;:::i;6742:98::-;;;;;;:::i;:::-;;:::i;6391:143::-;;;;;;:::i;:::-;;:::i;4963:1333::-;;;:::i;3468:1428::-;;;:::i;6540:95::-;6594:7;:9;;;:7;:9;;;:::i;:::-;;;;;;6613:15;6625:2;;6613:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;6613:11:0;;-1:-1:-1;;;6613:15:0:i;:::-;6540:95;;:::o;6949:84::-;7011:15;7023:2;;7011:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;7011:11:0;;-1:-1:-1;;;7011:15:0:i;6742:98::-;6798:7;:9;;;:7;:9;;;:::i;:::-;;;;-1:-1:-1;;6817:16:0;;;;;:4;;:12;;:16;;6830:2;;;;6817:16;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6742:98;;:::o;6391:143::-;6450:15;6462:2;;6450:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;6450:11:0;;-1:-1:-1;;;6450:15:0:i;:::-;6475:52;;;;;;;;;;;;;;;;;;6515:10;6475:11;:52::i;4963:1333::-;5004:63;;;;;;;;;;;;;;;;5039:26;;;;;5059:4;5039:26;;;1747:74:1;5004:63:0;;;5039:11;;;;1720:18:1;;5039:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5031:35;;5004:11;:63::i;:::-;5078:29;;;;;;;;;;;;;;;;;;:11;:29::i;:::-;3215:28;3207:37;;5117:12;;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5141:26:0;;;;;2327:2:1;5141:26:0;;;2309:21:1;2366:2;2346:18;;;2339:30;2405:14;2385:18;;;2378:42;5141:4:0;;-1:-1:-1;5141:10:0;;-1:-1:-1;2437:18:1;;5141:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5177:26:0;;;;;2668:2:1;5177:26:0;;;2650:21:1;2707:2;2687:18;;;2680:30;2746:14;2726:18;;;2719:42;5177:4:0;;-1:-1:-1;5177:10:0;;-1:-1:-1;2778:18:1;;5177:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5214:33;;;;;;;;;;;;;;;;;;:11;:33::i;:::-;5257:45;;;;;5291:8;5257:45;;;1747:74:1;5257:17:0;;;;1720:18:1;;5257:45:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5312:29:0;;;;;3009:2:1;5312:29:0;;;2991:21:1;3048:2;3028:18;;;3021:30;3087:17;3067:18;;;3060:45;5312:4:0;;-1:-1:-1;5312:10:0;;-1:-1:-1;3122:18:1;;5312:29:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5351:29:0;;;;;3353:2:1;5351:29:0;;;3335:21:1;3392:2;3372:18;;;3365:30;3431:17;3411:18;;;3404:45;5351:4:0;;-1:-1:-1;5351:10:0;;-1:-1:-1;3466:18:1;;5351:29:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5390:31:0;;;;;3697:2:1;5390:31:0;;;3679:21:1;3736:2;3716:18;;;3709:30;3775:16;3755:18;;;3748:44;5390:4:0;;-1:-1:-1;5390:13:0;;-1:-1:-1;3809:18:1;;5390:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3215:28;3207:37;;5431:16;;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5459:29:0;;;;;4040:2:1;5459:29:0;;;4022:21:1;4079:2;4059:18;;;4052:30;4118:17;4098:18;;;4091:45;5459:4:0;;-1:-1:-1;5459:10:0;;-1:-1:-1;4153:18:1;;5459:29:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5499;;;;;;;;;;;;;;;;;;:11;:29::i;:::-;5538:43;;;;;5572:6;5538:43;;;1747:74:1;5538:17:0;;;;1720:18:1;;5538:43:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;5591:22:0;;;;;4384:2:1;5591:22:0;;;4366:21:1;4423:1;4403:18;;;4396:29;4461:8;4441:18;;;4434:36;5591:4:0;;-1:-1:-1;5591:12:0;;-1:-1:-1;4487:18:1;;5591:22:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3215:28;3207:37;;5623:16;;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5652:34;;;;;;;;;;;;;;;;;;:11;:34::i;:::-;5696:40;;;;;5725:8;5696:40;;;1747:74:1;5696:12:0;;;;1720:18:1;;5696:40:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5746:8;5768:4;5757:16;;;;;:::i;:::-;160:25:1;;;148:2;133:18;5757:16:0;;;;;;;;;;;;;;;;;;;;;;;5746:27;;5791:1;:5;;;:7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5802:4;5791:15;5783:61;;;;;;;5100:2:1;5783:61:0;;;5082:21:1;5139:2;5119:18;;;5112:30;5178:34;5158:18;;;5151:62;5249:3;5229:18;;;5222:31;5270:19;;5783:61:0;;;;;;;;;5855:23;;;;;;;;;;;;;;;;;;:11;:23::i;:::-;5888:38;;;;;5917:6;5888:38;;;1747:74:1;5888:12:0;;;;1720:18:1;;5888:38:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5936:8;5980:2;5964:20;;5986:4;5947:44;;;;;:::i;:::-;160:25:1;;;148:2;133:18;5947:44:0;;;;;;;;;;;;;;;;;;;;;;;;;;;5936:55;;6009:1;:5;;;:7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;6020:4;6009:15;6001:62;;;;;;;5502:2:1;6001:62:0;;;5484:21:1;5541:2;5521:18;;;5514:30;5580:34;5560:18;;;5553:62;5651:4;5631:18;;;5624:32;5673:19;;6001:62:0;5300:398:1;6001:62:0;6073:20;;;;;;;;;;;;;;;;;;:11;:20::i;:::-;3215:28;3207:37;;6177:12;;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6212:4;6201:16;;;;;:::i;:::-;160:25:1;;;148:2;133:18;6201:16:0;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;6228:61:0;;;;;;;;;;;;;;;;6261:26;;;;;6281:4;6261:26;;;1747:74:1;6228:61:0;;;6261:11;;;;1720:18:1;;6261:26:0;1601:226:1;3468:1428:0;3509:31;;;;;;;;;5909:21:1;;;;5966:2;5946:18;;;5939:30;6005:14;5985:18;;;5978:42;3500:6:0;6072:20:1;;;6065:52;;;3500:6:0;3509:8;;;;6037:19:1;;3509:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3500:40;;3550:37;;;;;;;;;;;;;;;;;;3585:1;3550:11;:37::i;:::-;3598:43;;;;;;;;;;;;;;;;;;3635:4;3598:11;:43::i;:::-;3651:57;;;;;;;;;;;;;;;;3681:26;;;;;3701:4;3681:26;;;1747:74:1;3651:57:0;;;3681:11;;;;1720:18:1;;3681:26:0;1601:226:1;3651:57:0;3718:47;;;;;;;;;;;;;;;;;;3753:10;3718:11;:47::i;:::-;3775:61;;;;;;;;;;;;;;;;3803:32;;;;;3823:10;3803:32;;;1747:74:1;3775:61:0;;;3803:11;;;;1720:18:1;;3803:32:0;1601:226:1;3775:61:0;3847:55;;;;;;;;;;;;;;;;;3935:38;;;;;3847:18;;3935:16;;;;:38;;3847:55;;3935:38;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3912:61;;3983:37;;;;;;;;;;;;;;;;;;4003:4;4008:1;4003:7;;;;;;;;:::i;:::-;;;;;;;4012:4;4017:1;4012:7;;;;;;;;:::i;:::-;;;;;;;3983:11;:37::i;:::-;4031:27;;;;;10225:2:1;4031:27:0;;;10207:21:1;10264:2;10244:18;;;10237:30;10303:15;10283:18;;;10276:43;4031:4:0;;:10;;10336:18:1;;4031:27:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4068:37:0;;;;;4098:4;4068:37;;;1747:74:1;4068:13:0;;-1:-1:-1;4068:13:0;;-1:-1:-1;1720:18:1;;4068:37:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4115:26:0;;;;;10567:2:1;4115:26:0;;;10549:21:1;10606:2;10586:18;;;10579:30;10645:14;10625:18;;;10618:42;4115:4:0;;-1:-1:-1;4115:10:0;;-1:-1:-1;10677:18:1;;4115:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4151:59;;;;;;;;;;;;;;;;;;4198:10;4151:11;:59::i;:::-;4220:56;;;;;;;;;;;;;;;;;;4270:4;4220:11;:56::i;:::-;4286:26;;;;;10908:2:1;4286:26:0;;;10890:21:1;10947:2;10927:18;;;10920:30;10986:14;10966:18;;;10959:42;4286:4:0;;:10;;11018:18:1;;4286:26:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3215:28;3207:37;;4322:12;;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4346:33:0;;;;;11249:2:1;4346:33:0;;;11231:21:1;11288:2;11268:18;;;11261:30;11327:21;11307:18;;;11300:49;4346:4:0;;-1:-1:-1;4346:10:0;;-1:-1:-1;11366:18:1;;4346:33:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4593:53:0;;;;;11597:2:1;4593:53:0;;;11579:21:1;11636:2;11616:18;;;11609:30;11675:33;11655:18;;;11648:61;4521:35:0;;-1:-1:-1;4569:7:0;;-1:-1:-1;4569:7:0;;4521:35;;4569:7;;4593:18;;11726::1;;4593:53:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4569:78;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4657:34:0;;;;;1777:42:1;1765:55;;4657:34:0;;;1747:74:1;4657:18:0;;-1:-1:-1;4657:18:0;;-1:-1:-1;1720:18:1;;4657:34:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4713:51:0;;;;;4758:4;4713:51;;;1747:74:1;4701:9:0;;-1:-1:-1;4713:36:0;;;;-1:-1:-1;4713:36:0;;1720:18:1;;4713:51:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4701:63;;4774:84;;;;;;;;;;;;;;;;;;4856:1;4774:11;:84::i;:::-;4869:20;;;;;;;;;;;;;;;;;;:11;:20::i;:::-;3490:1406;;;;;3468:1428::o;2025:164::-;2080:59;2135:2;2096:42;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;2080:15;:59::i;:::-;2025:164;:::o;2577:188::-;2644:71;2707:2;2711;2660:54;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;2644:15;:71::i;2383:188::-;2450:71;2513:2;2517;2466:54;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;2450:15;:71::i;2195:182::-;2259:68;2319:2;2323;2275:51;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;2259:15;:68::i;2771:222::-;2862:81;2931:2;2935;2939;2878:64;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;2862:15;:81::i;:::-;2771:222;;;:::o;1500:133::-;1571:55;1618:7;1737:14;;1209:42;1910:2;1897:16;;1713:21;;1737:14;1897:16;1209:42;1946:5;1935:68;1926:77;;1863:150;;1639:380;:::o;-1:-1:-1:-;;;;;;;;:::o;196:592:1:-;267:6;275;328:2;316:9;307:7;303:23;299:32;296:52;;;344:1;341;334:12;296:52;384:9;371:23;413:18;454:2;446:6;443:14;440:34;;;470:1;467;460:12;440:34;508:6;497:9;493:22;483:32;;553:7;546:4;542:2;538:13;534:27;524:55;;575:1;572;565:12;524:55;615:2;602:16;641:2;633:6;630:14;627:34;;;657:1;654;647:12;627:34;702:7;697:2;688:6;684:2;680:15;676:24;673:37;670:57;;;723:1;720;713:12;670:57;754:2;746:11;;;;;776:6;;-1:-1:-1;196:592:1;;-1:-1:-1;;;;196:592:1:o;793:349::-;832:3;863:66;856:5;853:77;850:257;;963:77;960:1;953:88;1064:4;1061:1;1054:15;1092:4;1089:1;1082:15;850:257;-1:-1:-1;1134:1:1;1123:13;;793:349::o;1147:449::-;1306:2;1295:9;1288:21;1345:6;1340:2;1329:9;1325:18;1318:34;1402:6;1394;1389:2;1378:9;1374:18;1361:48;1458:1;1429:22;;;1453:2;1425:31;;;1418:42;;;;1512:2;1500:15;;;1517:66;1496:88;1481:104;1477:113;;1147:449;-1:-1:-1;1147:449:1:o;1832:288::-;1901:6;1954:2;1942:9;1933:7;1929:23;1925:32;1922:52;;;1970:1;1967;1960:12;1922:52;2002:9;1996:16;2052:18;2045:5;2041:30;2034:5;2031:41;2021:69;;2086:1;2083;2076:12;2021:69;2109:5;1832:288;-1:-1:-1;;;1832:288:1:o;4709:184::-;4779:6;4832:2;4820:9;4811:7;4807:23;4803:32;4800:52;;;4848:1;4845;4838:12;4800:52;-1:-1:-1;4871:16:1;;4709:184;-1:-1:-1;4709:184:1:o;6128:277::-;6195:6;6248:2;6236:9;6227:7;6223:23;6219:32;6216:52;;;6264:1;6261;6254:12;6216:52;6296:9;6290:16;6349:5;6342:13;6335:21;6328:5;6325:32;6315:60;;6371:1;6368;6361:12;6410:258;6482:1;6492:113;6506:6;6503:1;6500:13;6492:113;;;6582:11;;;6576:18;6563:11;;;6556:39;6528:2;6521:10;6492:113;;;6623:6;6620:1;6617:13;6614:48;;;6658:1;6649:6;6644:3;6640:16;6633:27;6614:48;;6410:258;;;:::o;6673:317::-;6715:3;6753:5;6747:12;6780:6;6775:3;6768:19;6796:63;6852:6;6845:4;6840:3;6836:14;6829:4;6822:5;6818:16;6796:63;:::i;:::-;6904:2;6892:15;6909:66;6888:88;6879:98;;;;6979:4;6875:109;;6673:317;-1:-1:-1;;6673:317:1:o;6995:493::-;7245:2;7234:9;7227:21;7208:4;7271:45;7312:2;7301:9;7297:18;7289:6;7271:45;:::i;:::-;7364:9;7356:6;7352:22;7347:2;7336:9;7332:18;7325:50;7399:2;7391:6;7384:18;7435:14;7430:2;7422:6;7418:15;7411:39;7479:2;7471:6;7467:15;7459:23;;;6995:493;;;;:::o;7493:184::-;7545:77;7542:1;7535:88;7642:4;7639:1;7632:15;7666:4;7663:1;7656:15;7682:334;7753:2;7747:9;7809:2;7799:13;;7814:66;7795:86;7783:99;;7912:18;7897:34;;7933:22;;;7894:62;7891:88;;;7959:18;;:::i;:::-;7995:2;7988:22;7682:334;;-1:-1:-1;7682:334:1:o;8021:437::-;8097:5;8131:18;8123:6;8120:30;8117:56;;;8153:18;;:::i;:::-;8191:116;8301:4;8232:66;8227:2;8219:6;8215:15;8211:88;8207:99;8191:116;:::i;:::-;8182:125;;8330:6;8323:5;8316:21;8370:3;8361:6;8356:3;8352:16;8349:25;8346:45;;;8387:1;8384;8377:12;8346:45;8400:52;8445:6;8438:4;8431:5;8427:16;8422:3;8400:52;:::i;8463:1366::-;8568:6;8599:2;8642;8630:9;8621:7;8617:23;8613:32;8610:52;;;8658:1;8655;8648:12;8610:52;8691:9;8685:16;8720:18;8761:2;8753:6;8750:14;8747:34;;;8777:1;8774;8767:12;8747:34;8815:6;8804:9;8800:22;8790:32;;8860:7;8853:4;8849:2;8845:13;8841:27;8831:55;;8882:1;8879;8872:12;8831:55;8911:2;8905:9;8933:2;8929;8926:10;8923:36;;;8939:18;;:::i;:::-;8985:2;8982:1;8978:10;9008:28;9032:2;9028;9024:11;9008:28;:::i;:::-;9070:15;;;9140:11;;;9136:20;;;9101:12;;;;9168:19;;;9165:39;;;9200:1;9197;9190:12;9165:39;9232:2;9228;9224:11;9213:22;;9244:555;9260:6;9255:3;9252:15;9244:555;;;9339:3;9333:10;9375:2;9362:11;9359:19;9356:109;;;9419:1;9448:2;9444;9437:14;9356:109;9488:20;;9543:2;9535:11;;9531:25;-1:-1:-1;9521:123:1;;9598:1;9627:2;9623;9616:14;9521:123;9669:87;9748:7;9742:2;9738;9734:11;9728:18;9723:2;9719;9715:11;9669:87;:::i;:::-;9657:100;;-1:-1:-1;9277:12:1;;;;9777;;;;9244:555;;;9818:5;8463:1366;-1:-1:-1;;;;;;;;;8463:1366:1:o;9834:184::-;9886:77;9883:1;9876:88;9983:4;9980:1;9973:15;10007:4;10004:1;9997:15;11755:458;11834:6;11887:2;11875:9;11866:7;11862:23;11858:32;11855:52;;;11903:1;11900;11893:12;11855:52;11936:9;11930:16;11969:18;11961:6;11958:30;11955:50;;;12001:1;11998;11991:12;11955:50;12024:22;;12077:4;12069:13;;12065:27;-1:-1:-1;12055:55:1;;12106:1;12103;12096:12;12055:55;12129:78;12199:7;12194:2;12188:9;12183:2;12179;12175:11;12129:78;:::i;:::-;12119:88;11755:458;-1:-1:-1;;;;11755:458:1:o;12218:338::-;12405:42;12397:6;12393:55;12382:9;12375:74;12485:2;12480;12469:9;12465:18;12458:30;12356:4;12505:45;12546:2;12535:9;12531:18;12523:6;12505:45;:::i;12561:220::-;12710:2;12699:9;12692:21;12673:4;12730:45;12771:2;12760:9;12756:18;12748:6;12730:45;:::i;12786:340::-;12963:2;12952:9;12945:21;12926:4;12983:45;13024:2;13013:9;13009:18;13001:6;12983:45;:::i;:::-;12975:53;;13076:42;13068:6;13064:55;13059:2;13048:9;13044:18;13037:83;12786:340;;;;;:::o;13131:291::-;13308:2;13297:9;13290:21;13271:4;13328:45;13369:2;13358:9;13354:18;13346:6;13328:45;:::i;:::-;13320:53;;13409:6;13404:2;13393:9;13389:18;13382:34;13131:291;;;;;:::o;13427:301::-;13598:2;13587:9;13580:21;13561:4;13618:45;13659:2;13648:9;13644:18;13636:6;13618:45;:::i;:::-;13610:53;;13713:6;13706:14;13699:22;13694:2;13683:9;13679:18;13672:50;13427:301;;;;;:::o;13733:546::-;13978:2;13967:9;13960:21;13941:4;14004:45;14045:2;14034:9;14030:18;14022:6;14004:45;:::i;:::-;14097:9;14089:6;14085:22;14080:2;14069:9;14065:18;14058:50;14131:33;14157:6;14149;14131:33;:::i;:::-;14117:47;;14212:9;14204:6;14200:22;14195:2;14184:9;14180:18;14173:50;14240:33;14266:6;14258;14240:33;:::i;:::-;14232:41;13733:546;-1:-1:-1;;;;;;13733:546:1:o","linkReferences":{}},"methodIdentifiers":{"call1(string)":"7e79255d","call2(string)":"8d3ef7ca","callPure(string)":"7f8b915c","counter()":"61bc221a","hello(string)":"a777d0dc","nested1(string)":"a76ccdfa","nested2(string)":"dbf1282f","run()":"c0406226","runBroadcast()":"bef03abc"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"call1\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"call2\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"callPure\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counter\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"hello\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"nested1\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_v\",\"type\":\"string\"}],\"name\":\"nested2\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"run\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"runBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"title\":\"ScriptExample\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"hello(string)\":{\"notice\":\"example external function, to force a CALL, and test vm.startPrank with.\"},\"run()\":{\"notice\":\"example function, runs through basic cheat-codes and console logs.\"},\"runBroadcast()\":{\"notice\":\"example function, to test vm.broadcast with.\"}},\"notice\":\"ScriptExample is an example script. The Go forge script code tests that it can run this.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"ScriptExample\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28\",\"dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"call1"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"call2"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"pure","type":"function","name":"callPure"},{"inputs":[],"stateMutability":"view","type":"function","name":"counter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"view","type":"function","name":"hello"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"nested1"},{"inputs":[{"internalType":"string","name":"_v","type":"string"}],"stateMutability":"nonpayable","type":"function","name":"nested2"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"run"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"runBroadcast"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{"hello(string)":{"notice":"example external function, to force a CALL, and test vm.startPrank with."},"run()":{"notice":"example function, runs through basic cheat-codes and console logs."},"runBroadcast()":{"notice":"example function, to test vm.broadcast with."}},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"ScriptExample"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da","urls":["bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28","dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[{"astId":243,"contract":"scripts/ScriptExample.s.sol:ScriptExample","label":"counter","offset":0,"slot":"0","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}},"userdoc":{"version":1,"kind":"user","methods":{"hello(string)":{"notice":"example external function, to force a CALL, and test vm.startPrank with."},"run()":{"notice":"example function, runs through basic cheat-codes and console logs."},"runBroadcast()":{"notice":"example function, to test vm.broadcast with."}},"notice":"ScriptExample is an example script. The Go forge script code tests that it can run this."},"devdoc":{"version":1,"kind":"dev","title":"ScriptExample"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":969,"exportedSymbols":{"FooBar":[799],"ForkTester":[968],"ForkedContract":[852],"NonceGetter":[833],"ScriptExample":[786],"Vm":[83],"console":[220]},"nodeType":"SourceUnit","src":"32:8375:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":83,"nodeType":"ContractDefinition","src":"120:969:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":83,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":61,"nodeType":"FunctionDefinition","src":"739:108:0","nodes":[],"functionSelector":"3ebf73b4","implemented":false,"kind":"function","modifiers":[],"name":"getDeployedCode","nameLocation":"748:15:0","parameters":{"id":57,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56,"mutability":"mutable","name":"artifactPath","nameLocation":"780:12:0","nodeType":"VariableDeclaration","scope":61,"src":"764:28:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":55,"name":"string","nodeType":"ElementaryTypeName","src":"764:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"763:30:0"},"returnParameters":{"id":60,"nodeType":"ParameterList","parameters":[{"constant":false,"id":59,"mutability":"mutable","name":"runtimeBytecode","nameLocation":"830:15:0","nodeType":"VariableDeclaration","scope":61,"src":"817:28:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":58,"name":"bytes","nodeType":"ElementaryTypeName","src":"817:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"816:30:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":68,"nodeType":"FunctionDefinition","src":"852:74:0","nodes":[],"functionSelector":"b4d6c782","implemented":false,"kind":"function","modifiers":[],"name":"etch","nameLocation":"861:4:0","parameters":{"id":66,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"target","nameLocation":"874:6:0","nodeType":"VariableDeclaration","scope":68,"src":"866:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":62,"name":"address","nodeType":"ElementaryTypeName","src":"866:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":65,"mutability":"mutable","name":"newRuntimeBytecode","nameLocation":"897:18:0","nodeType":"VariableDeclaration","scope":68,"src":"882:33:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":64,"name":"bytes","nodeType":"ElementaryTypeName","src":"882:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"865:51:0"},"returnParameters":{"id":67,"nodeType":"ParameterList","parameters":[],"src":"925:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":73,"nodeType":"FunctionDefinition","src":"931:51:0","nodes":[],"functionSelector":"ea060291","implemented":false,"kind":"function","modifiers":[],"name":"allowCheatcodes","nameLocation":"940:15:0","parameters":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"account","nameLocation":"964:7:0","nodeType":"VariableDeclaration","scope":73,"src":"956:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":69,"name":"address","nodeType":"ElementaryTypeName","src":"956:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"955:17:0"},"returnParameters":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"981:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":82,"nodeType":"FunctionDefinition","src":"987:100:0","nodes":[],"functionSelector":"71ee464d","implemented":false,"kind":"function","modifiers":[],"name":"createSelectFork","nameLocation":"996:16:0","parameters":{"id":78,"nodeType":"ParameterList","parameters":[{"constant":false,"id":75,"mutability":"mutable","name":"forkName","nameLocation":"1029:8:0","nodeType":"VariableDeclaration","scope":82,"src":"1013:24:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":74,"name":"string","nodeType":"ElementaryTypeName","src":"1013:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":77,"mutability":"mutable","name":"blockNumber","nameLocation":"1047:11:0","nodeType":"VariableDeclaration","scope":82,"src":"1039:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":76,"name":"uint256","nodeType":"ElementaryTypeName","src":"1039:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1012:47:0"},"returnParameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":82,"src":"1078:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":79,"name":"uint256","nodeType":"ElementaryTypeName","src":"1078:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1077:9:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[83],"name":"Vm","nameLocation":"130:2:0","scope":969,"usedErrors":[]},{"id":220,"nodeType":"ContractDefinition","src":"1144:1851:0","nodes":[{"id":89,"nodeType":"VariableDeclaration","src":"1166:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"1183:15:0","scope":220,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":84,"name":"address","nodeType":"ElementaryTypeName","src":"1166:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":87,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1209:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":86,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":85,"name":"address","nodeType":"ElementaryTypeName","src":"1201:7:0","typeDescriptions":{}}},"id":88,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1201:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1259:235:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1432:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1451:37:0","statements":[{"nodeType":"YulAssignment","src":"1465:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1474:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1465:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":95,"isOffset":false,"isSlot":false,"src":"1474:4:0","valueSize":1},{"declaration":102,"isOffset":false,"isSlot":false,"src":"1465:5:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1442:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"1268:25:0","parameters":{"id":96,"nodeType":"ParameterList","parameters":[{"constant":false,"id":95,"mutability":"mutable","name":"fnIn","nameLocation":"1331:4:0","nodeType":"VariableDeclaration","scope":106,"src":"1294:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":94,"nodeType":"FunctionTypeName","parameterTypes":{"id":92,"nodeType":"ParameterList","parameters":[{"constant":false,"id":91,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":94,"src":"1303:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":90,"name":"bytes","nodeType":"ElementaryTypeName","src":"1303:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1302:14:0"},"returnParameterTypes":{"id":93,"nodeType":"ParameterList","parameters":[],"src":"1331:0:0"},"src":"1294:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"1293:43:0"},"returnParameters":{"id":103,"nodeType":"ParameterList","parameters":[{"constant":false,"id":102,"mutability":"mutable","name":"fnOut","nameLocation":"1421:5:0","nodeType":"VariableDeclaration","scope":106,"src":"1384:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":101,"nodeType":"FunctionTypeName","parameterTypes":{"id":99,"nodeType":"ParameterList","parameters":[{"constant":false,"id":98,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":101,"src":"1393:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":97,"name":"bytes","nodeType":"ElementaryTypeName","src":"1393:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1392:14:0"},"returnParameterTypes":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"1421:0:0"},"src":"1384:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1383:44:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":118,"nodeType":"FunctionDefinition","src":"1500:133:0","nodes":[],"body":{"id":117,"nodeType":"Block","src":"1561:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":114,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1618:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":112,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":134,"src":"1597:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":111,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1571:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":113,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":115,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":116,"nodeType":"ExpressionStatement","src":"1571:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1509:15:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"payload","nameLocation":"1538:7:0","nodeType":"VariableDeclaration","scope":118,"src":"1525:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":107,"name":"bytes","nodeType":"ElementaryTypeName","src":"1525:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1524:22:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1561:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":134,"nodeType":"FunctionDefinition","src":"1639:380:0","nodes":[],"body":{"id":133,"nodeType":"Block","src":"1703:316:0","nodes":[],"statements":[{"assignments":[124],"declarations":[{"constant":false,"id":124,"mutability":"mutable","name":"payloadLength","nameLocation":"1721:13:0","nodeType":"VariableDeclaration","scope":133,"src":"1713:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":123,"name":"uint256","nodeType":"ElementaryTypeName","src":"1713:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":127,"initialValue":{"expression":{"id":125,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":120,"src":"1737:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":126,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1737:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1713:38:0"},{"assignments":[129],"declarations":[{"constant":false,"id":129,"mutability":"mutable","name":"consoleAddress","nameLocation":"1769:14:0","nodeType":"VariableDeclaration","scope":133,"src":"1761:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":128,"name":"address","nodeType":"ElementaryTypeName","src":"1761:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":131,"initialValue":{"id":130,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":89,"src":"1786:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1761:40:0"},{"AST":{"nodeType":"YulBlock","src":"1863:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1877:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1901:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1910:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1897:3:0"},"nodeType":"YulFunctionCall","src":"1897:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1881:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1926:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1946:3:0"},"nodeType":"YulFunctionCall","src":"1946:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1953:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1969:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1983:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1998:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"2001:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1935:10:0"},"nodeType":"YulFunctionCall","src":"1935:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1930:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":129,"isOffset":false,"isSlot":false,"src":"1953:14:0","valueSize":1},{"declaration":120,"isOffset":false,"isSlot":false,"src":"1901:7:0","valueSize":1},{"declaration":124,"isOffset":false,"isSlot":false,"src":"1983:13:0","valueSize":1}],"id":132,"nodeType":"InlineAssembly","src":"1854:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1648:19:0","parameters":{"id":121,"nodeType":"ParameterList","parameters":[{"constant":false,"id":120,"mutability":"mutable","name":"payload","nameLocation":"1681:7:0","nodeType":"VariableDeclaration","scope":134,"src":"1668:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":119,"name":"bytes","nodeType":"ElementaryTypeName","src":"1668:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1667:22:0"},"returnParameters":{"id":122,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":220,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":148,"nodeType":"FunctionDefinition","src":"2025:164:0","nodes":[],"body":{"id":147,"nodeType":"Block","src":"2070:119:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":142,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2120:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":143,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":136,"src":"2135:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":140,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2096:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":141,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2096: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":144,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2096:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":139,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2080:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":145,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2080:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":146,"nodeType":"ExpressionStatement","src":"2080:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2034:3:0","parameters":{"id":137,"nodeType":"ParameterList","parameters":[{"constant":false,"id":136,"mutability":"mutable","name":"p0","nameLocation":"2052:2:0","nodeType":"VariableDeclaration","scope":148,"src":"2038:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":135,"name":"string","nodeType":"ElementaryTypeName","src":"2038:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2037:18:0"},"returnParameters":{"id":138,"nodeType":"ParameterList","parameters":[],"src":"2070:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":165,"nodeType":"FunctionDefinition","src":"2195:182:0","nodes":[],"body":{"id":164,"nodeType":"Block","src":"2249:128:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":158,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2299:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":159,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":150,"src":"2319:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":160,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":152,"src":"2323:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":156,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2275:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":157,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2275: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":161,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2275:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":155,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2259:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":162,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2259:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":163,"nodeType":"ExpressionStatement","src":"2259:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2204:3:0","parameters":{"id":153,"nodeType":"ParameterList","parameters":[{"constant":false,"id":150,"mutability":"mutable","name":"p0","nameLocation":"2222:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2208:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":149,"name":"string","nodeType":"ElementaryTypeName","src":"2208:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":152,"mutability":"mutable","name":"p1","nameLocation":"2231:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2226:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":151,"name":"bool","nodeType":"ElementaryTypeName","src":"2226:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"2207:27:0"},"returnParameters":{"id":154,"nodeType":"ParameterList","parameters":[],"src":"2249:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":182,"nodeType":"FunctionDefinition","src":"2383:188:0","nodes":[],"body":{"id":181,"nodeType":"Block","src":"2440:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":175,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2490:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":176,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":167,"src":"2513:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":177,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":169,"src":"2517:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":173,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2466:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":174,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2466: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":178,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2466:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":172,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2450:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":179,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2450:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":180,"nodeType":"ExpressionStatement","src":"2450:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2392:3:0","parameters":{"id":170,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"mutability":"mutable","name":"p0","nameLocation":"2410:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2396:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":166,"name":"string","nodeType":"ElementaryTypeName","src":"2396:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":169,"mutability":"mutable","name":"p1","nameLocation":"2422:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2414:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":168,"name":"uint256","nodeType":"ElementaryTypeName","src":"2414:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2395:30:0"},"returnParameters":{"id":171,"nodeType":"ParameterList","parameters":[],"src":"2440:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":199,"nodeType":"FunctionDefinition","src":"2577:188:0","nodes":[],"body":{"id":198,"nodeType":"Block","src":"2634:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":192,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2684:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":193,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":184,"src":"2707:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":194,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"2711:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":190,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2660:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":191,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2660: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":195,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2660:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":189,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2644:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":196,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2644:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":197,"nodeType":"ExpressionStatement","src":"2644:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2586:3:0","parameters":{"id":187,"nodeType":"ParameterList","parameters":[{"constant":false,"id":184,"mutability":"mutable","name":"p0","nameLocation":"2604:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2590:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":183,"name":"string","nodeType":"ElementaryTypeName","src":"2590:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":186,"mutability":"mutable","name":"p1","nameLocation":"2616:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2608:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":185,"name":"address","nodeType":"ElementaryTypeName","src":"2608:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2589:30:0"},"returnParameters":{"id":188,"nodeType":"ParameterList","parameters":[],"src":"2634:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":219,"nodeType":"FunctionDefinition","src":"2771:222:0","nodes":[],"body":{"id":218,"nodeType":"Block","src":"2852:141:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":211,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2902:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":212,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":201,"src":"2931:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":213,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":203,"src":"2935:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":214,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":205,"src":"2939:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":209,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2878:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":210,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2878: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":215,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2878:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":208,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2862:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":216,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2862:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":217,"nodeType":"ExpressionStatement","src":"2862:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2780:3:0","parameters":{"id":206,"nodeType":"ParameterList","parameters":[{"constant":false,"id":201,"mutability":"mutable","name":"p0","nameLocation":"2798:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2784:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":200,"name":"string","nodeType":"ElementaryTypeName","src":"2784:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":203,"mutability":"mutable","name":"p1","nameLocation":"2816:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2802:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":202,"name":"string","nodeType":"ElementaryTypeName","src":"2802:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":205,"mutability":"mutable","name":"p2","nameLocation":"2834:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2820:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":204,"name":"string","nodeType":"ElementaryTypeName","src":"2820:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2783:54:0"},"returnParameters":{"id":207,"nodeType":"ParameterList","parameters":[],"src":"2852:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[220],"name":"console","nameLocation":"1152:7:0","scope":969,"usedErrors":[]},{"id":786,"nodeType":"ContractDefinition","src":"3123:3912:0","nodes":[{"id":235,"nodeType":"VariableDeclaration","src":"3152:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"3178:10:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":222,"name":"address","nodeType":"ElementaryTypeName","src":"3152:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3225:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":229,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"3215:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":231,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3215:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":228,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3207:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":227,"name":"uint256","nodeType":"ElementaryTypeName","src":"3207:7:0","typeDescriptions":{}}},"id":232,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3207:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":226,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3199:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":225,"name":"uint160","nodeType":"ElementaryTypeName","src":"3199:7:0","typeDescriptions":{}}},"id":233,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3199:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":224,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3191:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":223,"name":"address","nodeType":"ElementaryTypeName","src":"3191:7:0","typeDescriptions":{}}},"id":234,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3191:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":241,"nodeType":"VariableDeclaration","src":"3252:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"3273:2:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":237,"nodeType":"UserDefinedTypeName","pathNode":{"id":236,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"3252:2:0"},"referencedDeclaration":83,"src":"3252:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":239,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":235,"src":"3281:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":238,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"3278:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":240,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3278:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":243,"nodeType":"VariableDeclaration","src":"3356:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"3371:7:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":242,"name":"uint256","nodeType":"ElementaryTypeName","src":"3356:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":456,"nodeType":"FunctionDefinition","src":"3468:1428:0","nodes":[],"body":{"id":455,"nodeType":"Block","src":"3490:1406:0","nodes":[],"statements":[{"assignments":[248],"declarations":[{"constant":false,"id":248,"mutability":"mutable","name":"x","nameLocation":"3505:1:0","nodeType":"VariableDeclaration","scope":455,"src":"3500:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":247,"name":"bool","nodeType":"ElementaryTypeName","src":"3500:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":254,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":251,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3518:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":252,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"3534:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":249,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3509:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":250,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"3509:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"3500:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":258,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3562:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":259,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":248,"src":"3585:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":255,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3550:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":257,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":165,"src":"3550:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":260,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3550:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":261,"nodeType":"ExpressionStatement","src":"3550:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":265,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3610:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":268,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3635:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":267,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3627:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":266,"name":"address","nodeType":"ElementaryTypeName","src":"3627:7:0","typeDescriptions":{}}},"id":269,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3627:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":262,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3598:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3598:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3598:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":271,"nodeType":"ExpressionStatement","src":"3598:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":275,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3663:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":280,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3701:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":279,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3693:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":278,"name":"address","nodeType":"ElementaryTypeName","src":"3693:7:0","typeDescriptions":{}}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3693:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":276,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3681:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3681:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":282,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":272,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3651:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":274,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3651:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":283,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3651:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":284,"nodeType":"ExpressionStatement","src":"3651:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":288,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3730:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":291,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3753:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3753:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":290,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3745:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":289,"name":"address","nodeType":"ElementaryTypeName","src":"3745:7:0","typeDescriptions":{}}},"id":293,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3745:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":285,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3718:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3718:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3718:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":295,"nodeType":"ExpressionStatement","src":"3718:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":299,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3787:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":304,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3823:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":305,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3823:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":303,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3815:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":302,"name":"address","nodeType":"ElementaryTypeName","src":"3815:7:0","typeDescriptions":{}}},"id":306,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3815:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":300,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3803:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3803:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3803:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":296,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3775:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":298,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3775:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":308,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3775:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":309,"nodeType":"ExpressionStatement","src":"3775:61:0"},{"assignments":[311],"declarations":[{"constant":false,"id":311,"mutability":"mutable","name":"json","nameLocation":"3861:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3847:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":310,"name":"string","nodeType":"ElementaryTypeName","src":"3847:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":313,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3868:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3847:55:0"},{"assignments":[318],"declarations":[{"constant":false,"id":318,"mutability":"mutable","name":"keys","nameLocation":"3928:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3912:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":316,"name":"string","nodeType":"ElementaryTypeName","src":"3912:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":317,"nodeType":"ArrayTypeName","src":"3912:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":324,"initialValue":{"arguments":[{"id":321,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":311,"src":"3952:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3958:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":319,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3935:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":320,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3935:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":323,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3935:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3912:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":328,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3995:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":329,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4003:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":331,"indexExpression":{"hexValue":"30","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4008:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4003:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":332,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4012:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":334,"indexExpression":{"hexValue":"31","id":333,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4017:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4012:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":325,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3983:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":327,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":219,"src":"3983:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3983:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":336,"nodeType":"ExpressionStatement","src":"3983:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":340,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4042:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":337,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4031:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":339,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4031:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4031:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":342,"nodeType":"ExpressionStatement","src":"4031:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":350,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4098:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4090:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":348,"name":"uint160","nodeType":"ElementaryTypeName","src":"4090:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4090:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":347,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4082:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":346,"name":"address","nodeType":"ElementaryTypeName","src":"4082:7:0","typeDescriptions":{}}},"id":352,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4082:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":343,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4068:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":345,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"4068:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":353,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4068:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":354,"nodeType":"ExpressionStatement","src":"4068:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":358,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4126:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":355,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4115:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":357,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4115:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":359,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4115:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":360,"nodeType":"ExpressionStatement","src":"4115:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":364,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4163:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":367,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4198:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":368,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"4198:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":366,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4190:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":365,"name":"address","nodeType":"ElementaryTypeName","src":"4190:7:0","typeDescriptions":{}}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4190:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":361,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4151:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4151:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":370,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4151:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":371,"nodeType":"ExpressionStatement","src":"4151:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":375,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4232:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":378,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4270:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":377,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4262:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":376,"name":"address","nodeType":"ElementaryTypeName","src":"4262:7:0","typeDescriptions":{}}},"id":379,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4262:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":372,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4220:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":374,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4220:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":380,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4220:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":381,"nodeType":"ExpressionStatement","src":"4220:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4297:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":382,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4286:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4286:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":386,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4286:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":387,"nodeType":"ExpressionStatement","src":"4286:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4322:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":390,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"4322:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":391,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4322:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":392,"nodeType":"ExpressionStatement","src":"4322:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":396,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4357:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":393,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4346:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4346:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":397,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4346:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":398,"nodeType":"ExpressionStatement","src":"4346:33:0"},{"assignments":[400],"declarations":[{"constant":false,"id":400,"mutability":"mutable","name":"tmpNonceGetter","nameLocation":"4480:14:0","nodeType":"VariableDeclaration","scope":455,"src":"4472:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":399,"name":"address","nodeType":"ElementaryTypeName","src":"4472:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":413,"initialValue":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"74656d70206e6f6e6365207465737420676574746572","id":408,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4531:24:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""},"value":"temp nonce test getter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""}],"id":407,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"4521:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":409,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4521:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":406,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4513:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":405,"name":"uint256","nodeType":"ElementaryTypeName","src":"4513:7:0","typeDescriptions":{}}},"id":410,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4513:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":404,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4505:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":403,"name":"uint160","nodeType":"ElementaryTypeName","src":"4505:7:0","typeDescriptions":{}}},"id":411,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4505:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":402,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4497:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":401,"name":"address","nodeType":"ElementaryTypeName","src":"4497:7:0","typeDescriptions":{}}},"id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4497:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"4472:87:0"},{"expression":{"arguments":[{"id":417,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4577:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"hexValue":"5363726970744578616d706c652e732e736f6c3a4e6f6e6365476574746572","id":420,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4612:33:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""},"value":"ScriptExample.s.sol:NonceGetter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""}],"expression":{"id":418,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4593:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getDeployedCode","nodeType":"MemberAccess","referencedDeclaration":61,"src":"4593:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) view external returns (bytes memory)"}},"id":421,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4593:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":414,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4569:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":416,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"etch","nodeType":"MemberAccess","referencedDeclaration":68,"src":"4569:7:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory) external"}},"id":422,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4569:78:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":423,"nodeType":"ExpressionStatement","src":"4569:78:0"},{"expression":{"arguments":[{"id":427,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4676:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":424,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4657:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":426,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"allowCheatcodes","nodeType":"MemberAccess","referencedDeclaration":73,"src":"4657:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":428,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":429,"nodeType":"ExpressionStatement","src":"4657:34:0"},{"assignments":[431],"declarations":[{"constant":false,"id":431,"mutability":"mutable","name":"v","nameLocation":"4709:1:0","nodeType":"VariableDeclaration","scope":455,"src":"4701:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":430,"name":"uint256","nodeType":"ElementaryTypeName","src":"4701:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":441,"initialValue":{"arguments":[{"arguments":[{"id":438,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4758:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":437,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4750:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":436,"name":"address","nodeType":"ElementaryTypeName","src":"4750:7:0","typeDescriptions":{}}},"id":439,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4750:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"arguments":[{"id":433,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4725:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":432,"name":"NonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":833,"src":"4713:11:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_NonceGetter_$833_$","typeString":"type(contract NonceGetter)"}},"id":434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_NonceGetter_$833","typeString":"contract NonceGetter"}},"id":435,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":832,"src":"4713:36:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view external returns (uint256)"}},"id":440,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"4701:63:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683a","id":445,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4786:68:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},"value":"nonce from nonce getter, no explicit access required with vm.etch:"},{"id":446,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":431,"src":"4856:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":442,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4774:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":444,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"4774:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4774:84:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":448,"nodeType":"ExpressionStatement","src":"4774:84:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":452,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4881:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":449,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4869:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":451,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"4869:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4869:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":454,"nodeType":"ExpressionStatement","src":"4869:20:0"}]},"documentation":{"id":244,"nodeType":"StructuredDocumentation","src":"3385:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"3477:3:0","parameters":{"id":245,"nodeType":"ParameterList","parameters":[],"src":"3480:2:0"},"returnParameters":{"id":246,"nodeType":"ParameterList","parameters":[],"src":"3490:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":689,"nodeType":"FunctionDefinition","src":"4963:1333:0","nodes":[],"body":{"id":688,"nodeType":"Block","src":"4994:1302:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":463,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5016:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":470,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5059:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":469,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5051:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":468,"name":"address","nodeType":"ElementaryTypeName","src":"5051:7:0","typeDescriptions":{}}},"id":471,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5051:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":466,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5039:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":467,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5039:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5039:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":465,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5031:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":464,"name":"uint256","nodeType":"ElementaryTypeName","src":"5031:7:0","typeDescriptions":{}}},"id":473,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5031:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":460,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5004:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":462,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"5004:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":474,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5004:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":475,"nodeType":"ExpressionStatement","src":"5004:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":479,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5090:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":476,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5078:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":478,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5078:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":480,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5078:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":481,"nodeType":"ExpressionStatement","src":"5078:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":482,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5117:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5117:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":485,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5117:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":486,"nodeType":"ExpressionStatement","src":"5117:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":490,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5152:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":487,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5141:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":489,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5141:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":491,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5141:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":492,"nodeType":"ExpressionStatement","src":"5141:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":496,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5188:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":493,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5177:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5177:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":497,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5177:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":498,"nodeType":"ExpressionStatement","src":"5177:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":502,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5226:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":499,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5214:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5214:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":503,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5214:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":504,"nodeType":"ExpressionStatement","src":"5214:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5291:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":511,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5283:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":510,"name":"uint160","nodeType":"ElementaryTypeName","src":"5283:7:0","typeDescriptions":{}}},"id":513,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5283:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5275:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":508,"name":"address","nodeType":"ElementaryTypeName","src":"5275:7:0","typeDescriptions":{}}},"id":514,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5275:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":505,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5257:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":507,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5257:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":515,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5257:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":516,"nodeType":"ExpressionStatement","src":"5257:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":520,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5323:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":517,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5312:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":519,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5312:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":521,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5312:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":522,"nodeType":"ExpressionStatement","src":"5312:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":526,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5362:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":523,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5351:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":525,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5351:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5351:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":528,"nodeType":"ExpressionStatement","src":"5351:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":532,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5404:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":529,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5390:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":531,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":785,"src":"5390:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":533,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5390:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":534,"nodeType":"ExpressionStatement","src":"5390:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":535,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5431:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":537,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5431:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":538,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5431:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":539,"nodeType":"ExpressionStatement","src":"5431:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":543,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5470:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":540,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5459:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":542,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5459:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5459:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":545,"nodeType":"ExpressionStatement","src":"5459:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":549,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5511:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":546,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5499:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5499:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":550,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5499:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":551,"nodeType":"ExpressionStatement","src":"5499:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":559,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5572:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":558,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5564:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":557,"name":"uint160","nodeType":"ElementaryTypeName","src":"5564:7:0","typeDescriptions":{}}},"id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5564:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":556,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5556:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":555,"name":"address","nodeType":"ElementaryTypeName","src":"5556:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5556:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":552,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5538:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":554,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5538:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5538:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":563,"nodeType":"ExpressionStatement","src":"5538:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":567,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5604:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":564,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5591:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":758,"src":"5591:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":568,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5591:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":569,"nodeType":"ExpressionStatement","src":"5591:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":570,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5623:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5623:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5623:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"5623:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5664:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5652:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5652:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5652:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"5652:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":588,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5725:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":587,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5717:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":586,"name":"uint160","nodeType":"ElementaryTypeName","src":"5717:7:0","typeDescriptions":{}}},"id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5717:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":585,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5709:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":584,"name":"address","nodeType":"ElementaryTypeName","src":"5709:7:0","typeDescriptions":{}}},"id":590,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5709:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5696:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5696:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":591,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5696:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":592,"nodeType":"ExpressionStatement","src":"5696:40:0"},{"assignments":[595],"declarations":[{"constant":false,"id":595,"mutability":"mutable","name":"x","nameLocation":"5753:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5746:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":594,"nodeType":"UserDefinedTypeName","pathNode":{"id":593,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5746:6:0"},"referencedDeclaration":799,"src":"5746:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":601,"initialValue":{"arguments":[{"hexValue":"31323334","id":599,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5768:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":598,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5757:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":597,"nodeType":"UserDefinedTypeName","pathNode":{"id":596,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5761:6:0"},"referencedDeclaration":799,"src":"5761:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":600,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5757:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5746:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":607,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":603,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":595,"src":"5791:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"5791:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5791:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":606,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5802:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"5791:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323334","id":608,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5808:35:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""},"value":"FooBar: foo in create is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""}],"id":602,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"5783:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":609,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5783:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":610,"nodeType":"ExpressionStatement","src":"5783:61:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":614,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5867:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":611,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5855:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":613,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5855:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":615,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5855:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":616,"nodeType":"ExpressionStatement","src":"5855:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5917:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":623,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5909:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":622,"name":"uint160","nodeType":"ElementaryTypeName","src":"5909:7:0","typeDescriptions":{}}},"id":625,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5909:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":621,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5901:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":620,"name":"address","nodeType":"ElementaryTypeName","src":"5901:7:0","typeDescriptions":{}}},"id":626,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5901:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":617,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5888:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":627,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5888:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":628,"nodeType":"ExpressionStatement","src":"5888:38:0"},{"assignments":[631],"declarations":[{"constant":false,"id":631,"mutability":"mutable","name":"y","nameLocation":"5943:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5936:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":630,"nodeType":"UserDefinedTypeName","pathNode":{"id":629,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5936:6:0"},"referencedDeclaration":799,"src":"5936:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":645,"initialValue":{"arguments":[{"hexValue":"31323334","id":643,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5986:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":634,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5947:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":633,"nodeType":"UserDefinedTypeName","pathNode":{"id":632,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5951:6:0"},"referencedDeclaration":799,"src":"5951:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":642,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":639,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5980:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":638,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5972:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":637,"name":"uint256","nodeType":"ElementaryTypeName","src":"5972:7:0","typeDescriptions":{}}},"id":640,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5972:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":636,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5964:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":635,"name":"bytes32","nodeType":"ElementaryTypeName","src":"5964:7:0","typeDescriptions":{}}},"id":641,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5964:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"5947:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":644,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5947:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5936:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":651,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":647,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":631,"src":"6009:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":648,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"6009:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":649,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6009:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":650,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6020:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"6009:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e2063726561746532206973206e6f742031323334","id":652,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6026:36:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""},"value":"FooBar: foo in create2 is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""}],"id":646,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"6001:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6001:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":654,"nodeType":"ExpressionStatement","src":"6001:62:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":658,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6085:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":655,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6073:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":657,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6073:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":659,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6073:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":660,"nodeType":"ExpressionStatement","src":"6073:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":661,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6177:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"6177:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":664,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6177:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":665,"nodeType":"ExpressionStatement","src":"6177:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":669,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6212:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":668,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"6201:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":667,"nodeType":"UserDefinedTypeName","pathNode":{"id":666,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"6205:6:0"},"referencedDeclaration":799,"src":"6205:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":670,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6201:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":671,"nodeType":"ExpressionStatement","src":"6201:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":675,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6240:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":682,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6281:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":681,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6273:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":680,"name":"address","nodeType":"ElementaryTypeName","src":"6273:7:0","typeDescriptions":{}}},"id":683,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6273:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":678,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6261:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"6261:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":684,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6261:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":677,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6253:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":676,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:0","typeDescriptions":{}}},"id":685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6253:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":672,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6228:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":674,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"6228:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":686,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6228:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":687,"nodeType":"ExpressionStatement","src":"6228:61:0"}]},"documentation":{"id":457,"nodeType":"StructuredDocumentation","src":"4902:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"4972:12:0","parameters":{"id":458,"nodeType":"ParameterList","parameters":[],"src":"4984:2:0"},"returnParameters":{"id":459,"nodeType":"ParameterList","parameters":[],"src":"4994:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":713,"nodeType":"FunctionDefinition","src":"6391:143:0","nodes":[],"body":{"id":712,"nodeType":"Block","src":"6440:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":698,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":692,"src":"6462:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":695,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6450:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":697,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6450:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":699,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6450:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":700,"nodeType":"ExpressionStatement","src":"6450:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6487:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":707,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"6515:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":708,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"6515:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":706,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6507:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":705,"name":"address","nodeType":"ElementaryTypeName","src":"6507:7:0","typeDescriptions":{}}},"id":709,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6507:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":701,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6475:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"6475:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":710,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6475:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":711,"nodeType":"ExpressionStatement","src":"6475:52:0"}]},"documentation":{"id":690,"nodeType":"StructuredDocumentation","src":"6302:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"6400:5:0","parameters":{"id":693,"nodeType":"ParameterList","parameters":[{"constant":false,"id":692,"mutability":"mutable","name":"_v","nameLocation":"6422:2:0","nodeType":"VariableDeclaration","scope":713,"src":"6406:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":691,"name":"string","nodeType":"ElementaryTypeName","src":"6406:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6405:20:0"},"returnParameters":{"id":694,"nodeType":"ParameterList","parameters":[],"src":"6440:0:0"},"scope":786,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":728,"nodeType":"FunctionDefinition","src":"6540:95:0","nodes":[],"body":{"id":727,"nodeType":"Block","src":"6584:51:0","nodes":[],"statements":[{"expression":{"id":719,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6594:9:0","subExpression":{"id":718,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6594:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":720,"nodeType":"ExpressionStatement","src":"6594:9:0"},{"expression":{"arguments":[{"id":724,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":715,"src":"6625:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":721,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6613:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":723,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6613:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":725,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6613:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":726,"nodeType":"ExpressionStatement","src":"6613:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"6549:5:0","parameters":{"id":716,"nodeType":"ParameterList","parameters":[{"constant":false,"id":715,"mutability":"mutable","name":"_v","nameLocation":"6571:2:0","nodeType":"VariableDeclaration","scope":728,"src":"6555:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":714,"name":"string","nodeType":"ElementaryTypeName","src":"6555:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6554:20:0"},"returnParameters":{"id":717,"nodeType":"ParameterList","parameters":[],"src":"6584:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":743,"nodeType":"FunctionDefinition","src":"6641:95:0","nodes":[],"body":{"id":742,"nodeType":"Block","src":"6685:51:0","nodes":[],"statements":[{"expression":{"id":734,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6695:9:0","subExpression":{"id":733,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6695:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":735,"nodeType":"ExpressionStatement","src":"6695:9:0"},{"expression":{"arguments":[{"id":739,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":730,"src":"6726:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":736,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6714:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":738,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6714:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":740,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6714:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":741,"nodeType":"ExpressionStatement","src":"6714:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"6650:5:0","parameters":{"id":731,"nodeType":"ParameterList","parameters":[{"constant":false,"id":730,"mutability":"mutable","name":"_v","nameLocation":"6672:2:0","nodeType":"VariableDeclaration","scope":743,"src":"6656:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":729,"name":"string","nodeType":"ElementaryTypeName","src":"6656:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6655:20:0"},"returnParameters":{"id":732,"nodeType":"ParameterList","parameters":[],"src":"6685:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":758,"nodeType":"FunctionDefinition","src":"6742:98:0","nodes":[],"body":{"id":757,"nodeType":"Block","src":"6788:52:0","nodes":[],"statements":[{"expression":{"id":749,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6798:9:0","subExpression":{"id":748,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6798:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":750,"nodeType":"ExpressionStatement","src":"6798:9:0"},{"expression":{"arguments":[{"id":754,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":745,"src":"6830:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":751,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6817:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":753,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":773,"src":"6817:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":755,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6817:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":756,"nodeType":"ExpressionStatement","src":"6817:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"6751:7:0","parameters":{"id":746,"nodeType":"ParameterList","parameters":[{"constant":false,"id":745,"mutability":"mutable","name":"_v","nameLocation":"6775:2:0","nodeType":"VariableDeclaration","scope":758,"src":"6759:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":744,"name":"string","nodeType":"ElementaryTypeName","src":"6759:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6758:20:0"},"returnParameters":{"id":747,"nodeType":"ParameterList","parameters":[],"src":"6788:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":773,"nodeType":"FunctionDefinition","src":"6846:97:0","nodes":[],"body":{"id":772,"nodeType":"Block","src":"6892:51:0","nodes":[],"statements":[{"expression":{"id":764,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6902:9:0","subExpression":{"id":763,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6902:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":765,"nodeType":"ExpressionStatement","src":"6902:9:0"},{"expression":{"arguments":[{"id":769,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":760,"src":"6933:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":766,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6921:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":768,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6921:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":770,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6921:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":771,"nodeType":"ExpressionStatement","src":"6921:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"6855:7:0","parameters":{"id":761,"nodeType":"ParameterList","parameters":[{"constant":false,"id":760,"mutability":"mutable","name":"_v","nameLocation":"6879:2:0","nodeType":"VariableDeclaration","scope":773,"src":"6863:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":759,"name":"string","nodeType":"ElementaryTypeName","src":"6863:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6862:20:0"},"returnParameters":{"id":762,"nodeType":"ParameterList","parameters":[],"src":"6892:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":785,"nodeType":"FunctionDefinition","src":"6949:84:0","nodes":[],"body":{"id":784,"nodeType":"Block","src":"7001:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":781,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":775,"src":"7023:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":778,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"7011:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":780,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"7011:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":782,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7011:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":783,"nodeType":"ExpressionStatement","src":"7011:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"6958:8:0","parameters":{"id":776,"nodeType":"ParameterList","parameters":[{"constant":false,"id":775,"mutability":"mutable","name":"_v","nameLocation":"6983:2:0","nodeType":"VariableDeclaration","scope":785,"src":"6967:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":774,"name":"string","nodeType":"ElementaryTypeName","src":"6967:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6966:20:0"},"returnParameters":{"id":777,"nodeType":"ParameterList","parameters":[],"src":"7001:0:0"},"scope":786,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[799],"contractKind":"contract","documentation":{"id":221,"nodeType":"StructuredDocumentation","src":"2997:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[786],"name":"ScriptExample","nameLocation":"3132:13:0","scope":969,"usedErrors":[]},{"id":799,"nodeType":"ContractDefinition","src":"7037:96:0","nodes":[{"id":788,"nodeType":"VariableDeclaration","src":"7059:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"7074:3:0","scope":799,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":787,"name":"uint256","nodeType":"ElementaryTypeName","src":"7059:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":798,"nodeType":"FunctionDefinition","src":"7084:47:0","nodes":[],"body":{"id":797,"nodeType":"Block","src":"7107:24:0","nodes":[],"statements":[{"expression":{"id":795,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":793,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":788,"src":"7117:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":794,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":790,"src":"7123:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"7117:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":796,"nodeType":"ExpressionStatement","src":"7117:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":791,"nodeType":"ParameterList","parameters":[{"constant":false,"id":790,"mutability":"mutable","name":"v","nameLocation":"7104:1:0","nodeType":"VariableDeclaration","scope":798,"src":"7096:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":789,"name":"uint256","nodeType":"ElementaryTypeName","src":"7096:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7095:11:0"},"returnParameters":{"id":792,"nodeType":"ParameterList","parameters":[],"src":"7107:0:0"},"scope":799,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[799],"name":"FooBar","nameLocation":"7046:6:0","scope":969,"usedErrors":[]},{"id":833,"nodeType":"ContractDefinition","src":"7135:281:0","nodes":[{"id":813,"nodeType":"VariableDeclaration","src":"7162:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7188:10:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":800,"name":"address","nodeType":"ElementaryTypeName","src":"7162:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":808,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7235:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":807,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7225:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":809,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7225:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":806,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7217:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":805,"name":"uint256","nodeType":"ElementaryTypeName","src":"7217:7:0","typeDescriptions":{}}},"id":810,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7217:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":804,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7209:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":803,"name":"uint160","nodeType":"ElementaryTypeName","src":"7209:7:0","typeDescriptions":{}}},"id":811,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7209:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":802,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":801,"name":"address","nodeType":"ElementaryTypeName","src":"7201:7:0","typeDescriptions":{}}},"id":812,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7201:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":819,"nodeType":"VariableDeclaration","src":"7262:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7283:2:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":815,"nodeType":"UserDefinedTypeName","pathNode":{"id":814,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7262:2:0"},"referencedDeclaration":83,"src":"7262:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":817,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":813,"src":"7291:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":816,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7288:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":818,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7288:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":832,"nodeType":"FunctionDefinition","src":"7309:105:0","nodes":[],"body":{"id":831,"nodeType":"Block","src":"7372:42:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":828,"name":"_addr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":821,"src":"7401:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":826,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":819,"src":"7389:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":827,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7389:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":829,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7389:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"functionReturnParameters":825,"id":830,"nodeType":"Return","src":"7382:25:0"}]},"functionSelector":"2d0335ab","implemented":true,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"7318:8:0","parameters":{"id":822,"nodeType":"ParameterList","parameters":[{"constant":false,"id":821,"mutability":"mutable","name":"_addr","nameLocation":"7335:5:0","nodeType":"VariableDeclaration","scope":832,"src":"7327:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":820,"name":"address","nodeType":"ElementaryTypeName","src":"7327:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7326:15:0"},"returnParameters":{"id":825,"nodeType":"ParameterList","parameters":[{"constant":false,"id":824,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":832,"src":"7363:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":823,"name":"uint256","nodeType":"ElementaryTypeName","src":"7363:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7362:9:0"},"scope":833,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"NonceGetter","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[833],"name":"NonceGetter","nameLocation":"7144:11:0","scope":969,"usedErrors":[]},{"id":852,"nodeType":"ContractDefinition","src":"7418:174:0","nodes":[{"id":835,"nodeType":"VariableDeclaration","src":"7448:18:0","nodes":[],"constant":false,"mutability":"mutable","name":"v","nameLocation":"7465:1:0","scope":852,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":834,"name":"uint256","nodeType":"ElementaryTypeName","src":"7448:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"id":843,"nodeType":"FunctionDefinition","src":"7473:36:0","nodes":[],"body":{"id":842,"nodeType":"Block","src":"7487:22:0","nodes":[],"statements":[{"expression":{"id":840,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":838,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7497:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"31","id":839,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7501:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"7497:5:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":841,"nodeType":"ExpressionStatement","src":"7497:5:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":836,"nodeType":"ParameterList","parameters":[],"src":"7484:2:0"},"returnParameters":{"id":837,"nodeType":"ParameterList","parameters":[],"src":"7487:0:0"},"scope":852,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":851,"nodeType":"FunctionDefinition","src":"7515:75:0","nodes":[],"body":{"id":850,"nodeType":"Block","src":"7565:25:0","nodes":[],"statements":[{"expression":{"id":848,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7582:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":847,"id":849,"nodeType":"Return","src":"7575:8:0"}]},"functionSelector":"20965255","implemented":true,"kind":"function","modifiers":[],"name":"getValue","nameLocation":"7524:8:0","parameters":{"id":844,"nodeType":"ParameterList","parameters":[],"src":"7532:2:0"},"returnParameters":{"id":847,"nodeType":"ParameterList","parameters":[{"constant":false,"id":846,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":851,"src":"7556:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":845,"name":"uint256","nodeType":"ElementaryTypeName","src":"7556:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7555:9:0"},"scope":852,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkedContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[852],"name":"ForkedContract","nameLocation":"7427:14:0","scope":969,"usedErrors":[]},{"id":968,"nodeType":"ContractDefinition","src":"7594:813:0","nodes":[{"id":866,"nodeType":"VariableDeclaration","src":"7620:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7646:10:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":853,"name":"address","nodeType":"ElementaryTypeName","src":"7620:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":861,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7693:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":860,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7683:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":862,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7683:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":859,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7675:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":858,"name":"uint256","nodeType":"ElementaryTypeName","src":"7675:7:0","typeDescriptions":{}}},"id":863,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7675:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":857,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7667:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":856,"name":"uint160","nodeType":"ElementaryTypeName","src":"7667:7:0","typeDescriptions":{}}},"id":864,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7667:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":855,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7659:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":854,"name":"address","nodeType":"ElementaryTypeName","src":"7659:7:0","typeDescriptions":{}}},"id":865,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7659:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":872,"nodeType":"VariableDeclaration","src":"7720:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7741:2:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":868,"nodeType":"UserDefinedTypeName","pathNode":{"id":867,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7720:2:0"},"referencedDeclaration":83,"src":"7720:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":870,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":866,"src":"7749:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":869,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7746:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":871,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7746:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":967,"nodeType":"FunctionDefinition","src":"7767:638:0","nodes":[],"body":{"id":966,"nodeType":"Block","src":"7791:614:0","nodes":[],"statements":[{"assignments":[876],"declarations":[{"constant":false,"id":876,"mutability":"mutable","name":"testAddr","nameLocation":"7809:8:0","nodeType":"VariableDeclaration","scope":966,"src":"7801:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":875,"name":"address","nodeType":"ElementaryTypeName","src":"7801:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":884,"initialValue":{"arguments":[{"arguments":[{"hexValue":"307831323334","id":881,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7836:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":880,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7828:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":879,"name":"uint160","nodeType":"ElementaryTypeName","src":"7828:7:0","typeDescriptions":{}}},"id":882,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7828:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":878,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7820:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":877,"name":"address","nodeType":"ElementaryTypeName","src":"7820:7:0","typeDescriptions":{}}},"id":883,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7820:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"7801:43:0"},{"assignments":[887],"declarations":[{"constant":false,"id":887,"mutability":"mutable","name":"fc","nameLocation":"7869:2:0","nodeType":"VariableDeclaration","scope":966,"src":"7854:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"},"typeName":{"id":886,"nodeType":"UserDefinedTypeName","pathNode":{"id":885,"name":"ForkedContract","nodeType":"IdentifierPath","referencedDeclaration":852,"src":"7854:14:0"},"referencedDeclaration":852,"src":"7854:14:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"visibility":"internal"}],"id":891,"initialValue":{"arguments":[{"id":889,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7889:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":888,"name":"ForkedContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":852,"src":"7874:14:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ForkedContract_$852_$","typeString":"type(contract ForkedContract)"}},"id":890,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7874:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"nodeType":"VariableDeclarationStatement","src":"7854:44:0"},{"expression":{"arguments":[{"hexValue":"666f726b31","id":895,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7929:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},"value":"fork1"},{"hexValue":"3132333435","id":896,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7938:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"}],"expression":{"id":892,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7909:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":894,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"7909:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":897,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7909:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":898,"nodeType":"ExpressionStatement","src":"7909:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":905,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":902,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7974:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":900,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7962:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":901,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7962:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":903,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7962:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3132333435","id":904,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7987:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"},"src":"7962:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":906,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7994:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":899,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"7954:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":907,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7954:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":908,"nodeType":"ExpressionStatement","src":"7954:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":914,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":910,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8036:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":911,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8036:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":912,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8036:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31","id":913,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8053:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"8036:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652031","id":915,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8056:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""},"value":"value should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""}],"id":909,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8028:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":916,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8028:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":917,"nodeType":"ExpressionStatement","src":"8028:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":925,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":919,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8094:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":920,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8094:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"31","id":923,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8122:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"}],"id":922,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8114:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":921,"name":"uint256","nodeType":"ElementaryTypeName","src":"8114:7:0","typeDescriptions":{}}},"id":924,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8114:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8094:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652031","id":926,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8126:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""},"value":"balance should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""}],"id":918,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8086:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":927,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8086:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":928,"nodeType":"ExpressionStatement","src":"8086:62:0"},{"expression":{"arguments":[{"hexValue":"666f726b32","id":932,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8179:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},"value":"fork2"},{"hexValue":"3233343536","id":933,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8188:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"}],"expression":{"id":929,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8159:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":931,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"8159:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":934,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8159:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":935,"nodeType":"ExpressionStatement","src":"8159:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":939,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8224:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":937,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8212:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":938,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"8212:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":940,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8212:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3233343536","id":941,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8237:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"},"src":"8212:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":943,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8244:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":936,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8204:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":944,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8204:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":945,"nodeType":"ExpressionStatement","src":"8204:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":951,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":947,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8286:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":948,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8286:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":949,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8286:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"32","id":950,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8303:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"src":"8286:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652032","id":952,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8306:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""},"value":"value should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""}],"id":946,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8278:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":953,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8278:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":954,"nodeType":"ExpressionStatement","src":"8278:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":962,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":956,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8344:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":957,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8344:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"32","id":960,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8372:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"}],"id":959,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8364:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":958,"name":"uint256","nodeType":"ElementaryTypeName","src":"8364:7:0","typeDescriptions":{}}},"id":961,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8364:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8344:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652032","id":963,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8376:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""},"value":"balance should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""}],"id":955,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8336:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":964,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8336:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":965,"nodeType":"ExpressionStatement","src":"8336:62:0"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"7776:3:0","parameters":{"id":873,"nodeType":"ParameterList","parameters":[],"src":"7779:2:0"},"returnParameters":{"id":874,"nodeType":"ParameterList","parameters":[],"src":"7791:0:0"},"scope":968,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkTester","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[968],"name":"ForkTester","nameLocation":"7603:10:0","scope":969,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/Vm.json b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/Vm.json index 34175684c5aaa..3861727127444 100644 --- a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/Vm.json +++ b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/Vm.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"broadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"broadcast","inputs":[{"name":"msgSender","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"envOr","inputs":[{"name":"name","type":"string","internalType":"string"},{"name":"defaultValue","type":"bool","internalType":"bool"}],"outputs":[{"name":"value","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"getNonce","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"nonce","type":"uint64","internalType":"uint64"}],"stateMutability":"view"},{"type":"function","name":"parseJsonKeys","inputs":[{"name":"json","type":"string","internalType":"string"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"keys","type":"string[]","internalType":"string[]"}],"stateMutability":"pure"},{"type":"function","name":"startBroadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"startBroadcast","inputs":[{"name":"msgSender","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"startPrank","inputs":[{"name":"msgSender","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"stopBroadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"stopPrank","inputs":[],"outputs":[],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"broadcast()":"afc98040","broadcast(address)":"e6962cdb","envOr(string,bool)":"4777f3cf","getNonce(address)":"2d0335ab","parseJsonKeys(string,string)":"213e4198","startBroadcast()":"7fb5297f","startBroadcast(address)":"7fec2a8d","startPrank(address)":"06447d56","stopBroadcast()":"76eadd36","stopPrank()":"90c5013b"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"broadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"msgSender\",\"type\":\"address\"}],\"name\":\"broadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"defaultValue\",\"type\":\"bool\"}],\"name\":\"envOr\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"value\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"json\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"key\",\"type\":\"string\"}],\"name\":\"parseJsonKeys\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"keys\",\"type\":\"string[]\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"startBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"msgSender\",\"type\":\"address\"}],\"name\":\"startBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"msgSender\",\"type\":\"address\"}],\"name\":\"startPrank\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stopBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stopPrank\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"Vm\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3\",\"dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"broadcast"},{"inputs":[{"internalType":"address","name":"msgSender","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"broadcast"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"bool","name":"defaultValue","type":"bool"}],"stateMutability":"view","type":"function","name":"envOr","outputs":[{"internalType":"bool","name":"value","type":"bool"}]},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"getNonce","outputs":[{"internalType":"uint64","name":"nonce","type":"uint64"}]},{"inputs":[{"internalType":"string","name":"json","type":"string"},{"internalType":"string","name":"key","type":"string"}],"stateMutability":"pure","type":"function","name":"parseJsonKeys","outputs":[{"internalType":"string[]","name":"keys","type":"string[]"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"startBroadcast"},{"inputs":[{"internalType":"address","name":"msgSender","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"startBroadcast"},{"inputs":[{"internalType":"address","name":"msgSender","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"startPrank"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"stopBroadcast"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"stopPrank"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"Vm"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d","urls":["bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3","dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":720,"exportedSymbols":{"FooBar":[719],"ScriptExample":[706],"Vm":[55],"console":[192]},"nodeType":"SourceUnit","src":"32:5967:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":55,"nodeType":"ContractDefinition","src":"120:616:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":55,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[55],"name":"Vm","nameLocation":"130:2:0","scope":720,"usedErrors":[]},{"id":192,"nodeType":"ContractDefinition","src":"791:1622:0","nodes":[{"id":61,"nodeType":"VariableDeclaration","src":"813:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"830:15:0","scope":192,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":56,"name":"address","nodeType":"ElementaryTypeName","src":"813:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":59,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"856:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":58,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"848:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":57,"name":"address","nodeType":"ElementaryTypeName","src":"848:7:0","typeDescriptions":{}}},"id":60,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"848:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":78,"nodeType":"FunctionDefinition","src":"906:221:0","nodes":[],"body":{"id":77,"nodeType":"Block","src":"1065:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1084:37:0","statements":[{"nodeType":"YulAssignment","src":"1098:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1107:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1098:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":67,"isOffset":false,"isSlot":false,"src":"1107:4:0","valueSize":1},{"declaration":74,"isOffset":false,"isSlot":false,"src":"1098:5:0","valueSize":1}],"id":76,"nodeType":"InlineAssembly","src":"1075:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"915:25:0","parameters":{"id":68,"nodeType":"ParameterList","parameters":[{"constant":false,"id":67,"mutability":"mutable","name":"fnIn","nameLocation":"987:4:0","nodeType":"VariableDeclaration","scope":78,"src":"950:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":66,"nodeType":"FunctionTypeName","parameterTypes":{"id":64,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":66,"src":"959:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":62,"name":"bytes","nodeType":"ElementaryTypeName","src":"959:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"958:14:0"},"returnParameterTypes":{"id":65,"nodeType":"ParameterList","parameters":[],"src":"987:0:0"},"src":"950:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"940:57:0"},"returnParameters":{"id":75,"nodeType":"ParameterList","parameters":[{"constant":false,"id":74,"mutability":"mutable","name":"fnOut","nameLocation":"1058:5:0","nodeType":"VariableDeclaration","scope":78,"src":"1021:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":73,"nodeType":"FunctionTypeName","parameterTypes":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":73,"src":"1030:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":69,"name":"bytes","nodeType":"ElementaryTypeName","src":"1030:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1029:14:0"},"returnParameterTypes":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"1058:0:0"},"src":"1021:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1020:44:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":90,"nodeType":"FunctionDefinition","src":"1133:133:0","nodes":[],"body":{"id":89,"nodeType":"Block","src":"1194:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":86,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":80,"src":"1251:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":84,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1230:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":83,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":78,"src":"1204:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":85,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":87,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":88,"nodeType":"ExpressionStatement","src":"1204:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1142:15:0","parameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"payload","nameLocation":"1171:7:0","nodeType":"VariableDeclaration","scope":90,"src":"1158:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":79,"name":"bytes","nodeType":"ElementaryTypeName","src":"1158:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1157:22:0"},"returnParameters":{"id":82,"nodeType":"ParameterList","parameters":[],"src":"1194:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1272:380:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1336:316:0","nodes":[],"statements":[{"assignments":[96],"declarations":[{"constant":false,"id":96,"mutability":"mutable","name":"payloadLength","nameLocation":"1354:13:0","nodeType":"VariableDeclaration","scope":105,"src":"1346:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":95,"name":"uint256","nodeType":"ElementaryTypeName","src":"1346:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":99,"initialValue":{"expression":{"id":97,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":92,"src":"1370:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":98,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1370:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1346:38:0"},{"assignments":[101],"declarations":[{"constant":false,"id":101,"mutability":"mutable","name":"consoleAddress","nameLocation":"1402:14:0","nodeType":"VariableDeclaration","scope":105,"src":"1394:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":100,"name":"address","nodeType":"ElementaryTypeName","src":"1394:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":103,"initialValue":{"id":102,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":61,"src":"1419:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1394:40:0"},{"AST":{"nodeType":"YulBlock","src":"1496:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1510:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1534:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1543:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1530:3:0"},"nodeType":"YulFunctionCall","src":"1530:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1514:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1559:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1579:3:0"},"nodeType":"YulFunctionCall","src":"1579:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1586:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1602:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1616:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1631:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"1634:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1568:10:0"},"nodeType":"YulFunctionCall","src":"1568:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1563:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":101,"isOffset":false,"isSlot":false,"src":"1586:14:0","valueSize":1},{"declaration":92,"isOffset":false,"isSlot":false,"src":"1534:7:0","valueSize":1},{"declaration":96,"isOffset":false,"isSlot":false,"src":"1616:13:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1487:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1281:19:0","parameters":{"id":93,"nodeType":"ParameterList","parameters":[{"constant":false,"id":92,"mutability":"mutable","name":"payload","nameLocation":"1314:7:0","nodeType":"VariableDeclaration","scope":106,"src":"1301:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":91,"name":"bytes","nodeType":"ElementaryTypeName","src":"1301:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1300:22:0"},"returnParameters":{"id":94,"nodeType":"ParameterList","parameters":[],"src":"1336:0:0"},"scope":192,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":120,"nodeType":"FunctionDefinition","src":"1658:121:0","nodes":[],"body":{"id":119,"nodeType":"Block","src":"1703:76:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":114,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1753:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":115,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1768:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":112,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1729:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":113,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1729: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":116,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1729:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":111,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1713:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":117,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1713:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":118,"nodeType":"ExpressionStatement","src":"1713:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1667:3:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"p0","nameLocation":"1685:2:0","nodeType":"VariableDeclaration","scope":120,"src":"1671:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":107,"name":"string","nodeType":"ElementaryTypeName","src":"1671:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"1670:18:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":137,"nodeType":"FunctionDefinition","src":"1785:139:0","nodes":[],"body":{"id":136,"nodeType":"Block","src":"1839:85:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":130,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1889:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":131,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":122,"src":"1909:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":132,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":124,"src":"1913:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":128,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1865:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":129,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1865: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":133,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1865:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":127,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1849:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":134,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1849:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":135,"nodeType":"ExpressionStatement","src":"1849:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1794:3:0","parameters":{"id":125,"nodeType":"ParameterList","parameters":[{"constant":false,"id":122,"mutability":"mutable","name":"p0","nameLocation":"1812:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1798:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":121,"name":"string","nodeType":"ElementaryTypeName","src":"1798:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":124,"mutability":"mutable","name":"p1","nameLocation":"1821:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1816:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":123,"name":"bool","nodeType":"ElementaryTypeName","src":"1816:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"1797:27:0"},"returnParameters":{"id":126,"nodeType":"ParameterList","parameters":[],"src":"1839:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":154,"nodeType":"FunctionDefinition","src":"1930:145:0","nodes":[],"body":{"id":153,"nodeType":"Block","src":"1987:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":147,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2037:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":148,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":139,"src":"2060:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":149,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":141,"src":"2064:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":145,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2013:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":146,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2013: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":150,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2013:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":144,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1997:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":151,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1997:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":152,"nodeType":"ExpressionStatement","src":"1997:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1939:3:0","parameters":{"id":142,"nodeType":"ParameterList","parameters":[{"constant":false,"id":139,"mutability":"mutable","name":"p0","nameLocation":"1957:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1943:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":138,"name":"string","nodeType":"ElementaryTypeName","src":"1943:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":141,"mutability":"mutable","name":"p1","nameLocation":"1969:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1961:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":140,"name":"uint256","nodeType":"ElementaryTypeName","src":"1961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1942:30:0"},"returnParameters":{"id":143,"nodeType":"ParameterList","parameters":[],"src":"1987:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":171,"nodeType":"FunctionDefinition","src":"2081:145:0","nodes":[],"body":{"id":170,"nodeType":"Block","src":"2138:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":164,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2188:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":165,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":156,"src":"2211:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":166,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":158,"src":"2215:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":162,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2164:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":163,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2164: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":167,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2164:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":161,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2148:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":168,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2148:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":169,"nodeType":"ExpressionStatement","src":"2148:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2090:3:0","parameters":{"id":159,"nodeType":"ParameterList","parameters":[{"constant":false,"id":156,"mutability":"mutable","name":"p0","nameLocation":"2108:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2094:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":155,"name":"string","nodeType":"ElementaryTypeName","src":"2094:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":158,"mutability":"mutable","name":"p1","nameLocation":"2120:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2112:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":157,"name":"address","nodeType":"ElementaryTypeName","src":"2112:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2093:30:0"},"returnParameters":{"id":160,"nodeType":"ParameterList","parameters":[],"src":"2138:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":191,"nodeType":"FunctionDefinition","src":"2232:179:0","nodes":[],"body":{"id":190,"nodeType":"Block","src":"2313:98:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":183,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2363:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":184,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":173,"src":"2392:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":185,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":175,"src":"2396:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":186,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":177,"src":"2400:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":181,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2339:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":182,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2339: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":187,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2339:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":180,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2323:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":188,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2323:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":189,"nodeType":"ExpressionStatement","src":"2323:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2241:3:0","parameters":{"id":178,"nodeType":"ParameterList","parameters":[{"constant":false,"id":173,"mutability":"mutable","name":"p0","nameLocation":"2259:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2245:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":172,"name":"string","nodeType":"ElementaryTypeName","src":"2245:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":175,"mutability":"mutable","name":"p1","nameLocation":"2277:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2263:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":174,"name":"string","nodeType":"ElementaryTypeName","src":"2263:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":177,"mutability":"mutable","name":"p2","nameLocation":"2295:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2281:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":176,"name":"string","nodeType":"ElementaryTypeName","src":"2281:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2244:54:0"},"returnParameters":{"id":179,"nodeType":"ParameterList","parameters":[],"src":"2313:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[192],"name":"console","nameLocation":"799:7:0","scope":720,"usedErrors":[]},{"id":706,"nodeType":"ContractDefinition","src":"2541:3359:0","nodes":[{"id":207,"nodeType":"VariableDeclaration","src":"2571:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"2597:10:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":194,"name":"address","nodeType":"ElementaryTypeName","src":"2571:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":202,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2644:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":201,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"2634:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":203,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2634:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":200,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2626:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":199,"name":"uint256","nodeType":"ElementaryTypeName","src":"2626:7:0","typeDescriptions":{}}},"id":204,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2626:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":198,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2618:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":197,"name":"uint160","nodeType":"ElementaryTypeName","src":"2618:7:0","typeDescriptions":{}}},"id":205,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2618:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":196,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2610:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":195,"name":"address","nodeType":"ElementaryTypeName","src":"2610:7:0","typeDescriptions":{}}},"id":206,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2610:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":213,"nodeType":"VariableDeclaration","src":"2671:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"2692:2:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"},"typeName":{"id":209,"nodeType":"UserDefinedTypeName","pathNode":{"id":208,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":55,"src":"2671:2:0"},"referencedDeclaration":55,"src":"2671:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"value":{"arguments":[{"id":211,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":207,"src":"2700:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":210,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":55,"src":"2697:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$55_$","typeString":"type(contract Vm)"}},"id":212,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2697:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"visibility":"internal"},{"id":215,"nodeType":"VariableDeclaration","src":"2775:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"2790:7:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":214,"name":"uint256","nodeType":"ElementaryTypeName","src":"2775:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":378,"nodeType":"FunctionDefinition","src":"2887:949:0","nodes":[],"body":{"id":377,"nodeType":"Block","src":"2909:927:0","nodes":[],"statements":[{"assignments":[220],"declarations":[{"constant":false,"id":220,"mutability":"mutable","name":"x","nameLocation":"2924:1:0","nodeType":"VariableDeclaration","scope":377,"src":"2919:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":219,"name":"bool","nodeType":"ElementaryTypeName","src":"2919:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":226,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":223,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2937:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":224,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"2953:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":221,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"2928:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":222,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"2928:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":225,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2928:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"2919:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2981:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":231,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3004:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":227,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"2969:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":229,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":137,"src":"2969:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":232,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2969:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":233,"nodeType":"ExpressionStatement","src":"2969:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":237,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3029:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":240,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3054:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":239,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3046:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":238,"name":"address","nodeType":"ElementaryTypeName","src":"3046:7:0","typeDescriptions":{}}},"id":241,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3046:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":234,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3017:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":236,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3017:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":242,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3017:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":243,"nodeType":"ExpressionStatement","src":"3017:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":247,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3082:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":252,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3120:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":251,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3112:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":250,"name":"address","nodeType":"ElementaryTypeName","src":"3112:7:0","typeDescriptions":{}}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3112:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":248,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3100:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":249,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3100:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":254,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3100:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":244,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3070:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":246,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3070:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":255,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3070:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":256,"nodeType":"ExpressionStatement","src":"3070:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":260,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3149:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":263,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3172:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3172:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":262,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3164:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":261,"name":"address","nodeType":"ElementaryTypeName","src":"3164:7:0","typeDescriptions":{}}},"id":265,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3164:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":257,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3137:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":259,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3137:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":266,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3137:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":267,"nodeType":"ExpressionStatement","src":"3137:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":271,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3206:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":276,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3242:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3242:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":275,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3234:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":274,"name":"address","nodeType":"ElementaryTypeName","src":"3234:7:0","typeDescriptions":{}}},"id":278,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3234:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":272,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3222:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":273,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3222:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":279,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3222:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":268,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3194:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3194:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":280,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3194:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":281,"nodeType":"ExpressionStatement","src":"3194:61:0"},{"assignments":[283],"declarations":[{"constant":false,"id":283,"mutability":"mutable","name":"json","nameLocation":"3280:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3266:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":282,"name":"string","nodeType":"ElementaryTypeName","src":"3266:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":285,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":284,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3287:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3266:55:0"},{"assignments":[290],"declarations":[{"constant":false,"id":290,"mutability":"mutable","name":"keys","nameLocation":"3347:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3331:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":288,"name":"string","nodeType":"ElementaryTypeName","src":"3331:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":289,"nodeType":"ArrayTypeName","src":"3331:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":296,"initialValue":{"arguments":[{"id":293,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":283,"src":"3371:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":294,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3377:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":291,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3354:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3354:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":295,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3354:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3331:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":300,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3414:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":301,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3422:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":303,"indexExpression":{"hexValue":"30","id":302,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3427:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3422:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":304,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3431:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":306,"indexExpression":{"hexValue":"31","id":305,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3436:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3431:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":297,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3402:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":299,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":191,"src":"3402:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3402:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":308,"nodeType":"ExpressionStatement","src":"3402:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3461:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":309,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3450:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":311,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3450:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":313,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3450:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":314,"nodeType":"ExpressionStatement","src":"3450:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3517:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":321,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3509:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":320,"name":"uint160","nodeType":"ElementaryTypeName","src":"3509:7:0","typeDescriptions":{}}},"id":323,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":319,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3501:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":318,"name":"address","nodeType":"ElementaryTypeName","src":"3501:7:0","typeDescriptions":{}}},"id":324,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3501:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":315,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3487:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":317,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"3487:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":325,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3487:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":326,"nodeType":"ExpressionStatement","src":"3487:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3545:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":327,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3534:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":329,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3534:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":331,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3534:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":332,"nodeType":"ExpressionStatement","src":"3534:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":336,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3582:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":339,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3617:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":340,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3617:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":338,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3609:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":337,"name":"address","nodeType":"ElementaryTypeName","src":"3609:7:0","typeDescriptions":{}}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3609:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":333,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3570:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3570:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":342,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3570:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":343,"nodeType":"ExpressionStatement","src":"3570:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":347,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3651:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":350,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3689:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3681:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":348,"name":"address","nodeType":"ElementaryTypeName","src":"3681:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":344,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3639:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":346,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3639:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":352,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3639:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":353,"nodeType":"ExpressionStatement","src":"3639:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":357,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3716:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":354,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3705:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":356,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3705:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":358,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3705:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":359,"nodeType":"ExpressionStatement","src":"3705:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":360,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3741:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":362,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"3741:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3741:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":364,"nodeType":"ExpressionStatement","src":"3741:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":368,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3776:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":365,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3765:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":367,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3765:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3765:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":370,"nodeType":"ExpressionStatement","src":"3765:33:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":374,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3821:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":371,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3809:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":373,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"3809:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":375,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3809:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":376,"nodeType":"ExpressionStatement","src":"3809:20:0"}]},"documentation":{"id":216,"nodeType":"StructuredDocumentation","src":"2804:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"2896:3:0","parameters":{"id":217,"nodeType":"ParameterList","parameters":[],"src":"2899:2:0"},"returnParameters":{"id":218,"nodeType":"ParameterList","parameters":[],"src":"2909:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":609,"nodeType":"FunctionDefinition","src":"3903:1258:0","nodes":[],"body":{"id":608,"nodeType":"Block","src":"3934:1227:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3956:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":392,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3999:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":391,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3991:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":390,"name":"address","nodeType":"ElementaryTypeName","src":"3991:7:0","typeDescriptions":{}}},"id":393,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3991:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3979:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":389,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3979:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":394,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3979:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":387,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3971:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":386,"name":"uint256","nodeType":"ElementaryTypeName","src":"3971:7:0","typeDescriptions":{}}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3971:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":382,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3944:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3944:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":396,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3944:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":397,"nodeType":"ExpressionStatement","src":"3944:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":401,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4030:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":398,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4018:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":400,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4018:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":402,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4018:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":403,"nodeType":"ExpressionStatement","src":"4018:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":404,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4057:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":406,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"4057:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":407,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4057:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":408,"nodeType":"ExpressionStatement","src":"4057:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4092:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":409,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4081:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":411,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4081:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":413,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4081:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":414,"nodeType":"ExpressionStatement","src":"4081:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":418,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4128:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":415,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4117:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":417,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4117:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4117:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":420,"nodeType":"ExpressionStatement","src":"4117:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":424,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4166:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":421,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4154:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":423,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4154:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":425,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4154:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":426,"nodeType":"ExpressionStatement","src":"4154:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":434,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4231:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":433,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4223:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":432,"name":"uint160","nodeType":"ElementaryTypeName","src":"4223:7:0","typeDescriptions":{}}},"id":435,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4223:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":431,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4215:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":430,"name":"address","nodeType":"ElementaryTypeName","src":"4215:7:0","typeDescriptions":{}}},"id":436,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4215:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":427,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4197:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":429,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4197:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":437,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4197:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":438,"nodeType":"ExpressionStatement","src":"4197:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":442,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4263:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":439,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4252:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":441,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4252:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":443,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4252:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":444,"nodeType":"ExpressionStatement","src":"4252:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":448,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4302:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":445,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4291:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4291:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":449,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4291:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":450,"nodeType":"ExpressionStatement","src":"4291:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":454,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4344:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":451,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4330:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":705,"src":"4330:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":455,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4330:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":456,"nodeType":"ExpressionStatement","src":"4330:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":457,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4371:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":459,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4371:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":460,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4371:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":461,"nodeType":"ExpressionStatement","src":"4371:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":465,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4410:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":462,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4399:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":464,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4399:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":466,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4399:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":467,"nodeType":"ExpressionStatement","src":"4399:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":471,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4451:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":468,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4439:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":470,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4439:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4439:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":473,"nodeType":"ExpressionStatement","src":"4439:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":481,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4512:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":480,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4504:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":479,"name":"uint160","nodeType":"ElementaryTypeName","src":"4504:7:0","typeDescriptions":{}}},"id":482,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4504:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":478,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4496:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":477,"name":"address","nodeType":"ElementaryTypeName","src":"4496:7:0","typeDescriptions":{}}},"id":483,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4496:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":474,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4478:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":476,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4478:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4478:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":485,"nodeType":"ExpressionStatement","src":"4478:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":489,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4544:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":486,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4531:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":488,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":678,"src":"4531:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":490,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4531:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":491,"nodeType":"ExpressionStatement","src":"4531:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":492,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4563:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":494,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4563:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4563:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":496,"nodeType":"ExpressionStatement","src":"4563:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":500,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4604:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":497,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4592:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":499,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4592:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4592:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":502,"nodeType":"ExpressionStatement","src":"4592:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":510,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4665:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4657:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":508,"name":"uint160","nodeType":"ElementaryTypeName","src":"4657:7:0","typeDescriptions":{}}},"id":511,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":507,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4649:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":506,"name":"address","nodeType":"ElementaryTypeName","src":"4649:7:0","typeDescriptions":{}}},"id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4649:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":503,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4636:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":505,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4636:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":513,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4636:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":514,"nodeType":"ExpressionStatement","src":"4636:40:0"},{"assignments":[517],"declarations":[{"constant":false,"id":517,"mutability":"mutable","name":"x","nameLocation":"4693:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4686:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":516,"nodeType":"UserDefinedTypeName","pathNode":{"id":515,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4686:6:0"},"referencedDeclaration":719,"src":"4686:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":523,"initialValue":{"arguments":[{"hexValue":"31323334","id":521,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4708:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":520,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4697:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":519,"nodeType":"UserDefinedTypeName","pathNode":{"id":518,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4701:6:0"},"referencedDeclaration":719,"src":"4701:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":522,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4697:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4686:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":529,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":525,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":517,"src":"4731:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":526,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4731:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4731:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":528,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4742:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4731:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":524,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4723:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":530,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4723:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":531,"nodeType":"ExpressionStatement","src":"4723:24:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":535,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4770:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":532,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4758:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":534,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4758:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":536,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4758:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":537,"nodeType":"ExpressionStatement","src":"4758:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":545,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4820:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":544,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4812:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":543,"name":"uint160","nodeType":"ElementaryTypeName","src":"4812:7:0","typeDescriptions":{}}},"id":546,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4812:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":542,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4804:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":541,"name":"address","nodeType":"ElementaryTypeName","src":"4804:7:0","typeDescriptions":{}}},"id":547,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4804:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":538,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4791:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":540,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4791:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4791:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":549,"nodeType":"ExpressionStatement","src":"4791:38:0"},{"assignments":[552],"declarations":[{"constant":false,"id":552,"mutability":"mutable","name":"y","nameLocation":"4846:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4839:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":551,"nodeType":"UserDefinedTypeName","pathNode":{"id":550,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4839:6:0"},"referencedDeclaration":719,"src":"4839:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":566,"initialValue":{"arguments":[{"hexValue":"31323334","id":564,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4889:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":555,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4850:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":554,"nodeType":"UserDefinedTypeName","pathNode":{"id":553,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4854:6:0"},"referencedDeclaration":719,"src":"4854:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":563,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4883:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":559,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4875:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":558,"name":"uint256","nodeType":"ElementaryTypeName","src":"4875:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4875:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":557,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4867:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":556,"name":"bytes32","nodeType":"ElementaryTypeName","src":"4867:7:0","typeDescriptions":{}}},"id":562,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4867:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"4850:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":565,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4850:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4839:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":568,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":552,"src":"4912:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":569,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4912:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":570,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4912:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":571,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4923:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4912:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":567,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4904:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4904:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"4904:24:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4950:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4938:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4938:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4938:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"4938:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5042:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5042:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":584,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5042:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":585,"nodeType":"ExpressionStatement","src":"5042:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5077:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":588,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5066:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":587,"nodeType":"UserDefinedTypeName","pathNode":{"id":586,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"5070:6:0"},"referencedDeclaration":719,"src":"5070:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":590,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5066:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":591,"nodeType":"ExpressionStatement","src":"5066:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":595,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5105:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":602,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5146:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":601,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5138:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":600,"name":"address","nodeType":"ElementaryTypeName","src":"5138:7:0","typeDescriptions":{}}},"id":603,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5138:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":598,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5126:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":599,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5126:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5126:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":597,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5118:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":596,"name":"uint256","nodeType":"ElementaryTypeName","src":"5118:7:0","typeDescriptions":{}}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5118:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":592,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5093:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":594,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"5093:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":606,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5093:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":607,"nodeType":"ExpressionStatement","src":"5093:61:0"}]},"documentation":{"id":379,"nodeType":"StructuredDocumentation","src":"3842:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"3912:12:0","parameters":{"id":380,"nodeType":"ParameterList","parameters":[],"src":"3924:2:0"},"returnParameters":{"id":381,"nodeType":"ParameterList","parameters":[],"src":"3934:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":633,"nodeType":"FunctionDefinition","src":"5256:143:0","nodes":[],"body":{"id":632,"nodeType":"Block","src":"5305:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":618,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":612,"src":"5327:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":615,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5315:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":617,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5315:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5315:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":620,"nodeType":"ExpressionStatement","src":"5315:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5352:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":627,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"5380:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":628,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"5380:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":626,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5372:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":625,"name":"address","nodeType":"ElementaryTypeName","src":"5372:7:0","typeDescriptions":{}}},"id":629,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5372:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":621,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5340:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":623,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"5340:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":630,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5340:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":631,"nodeType":"ExpressionStatement","src":"5340:52:0"}]},"documentation":{"id":610,"nodeType":"StructuredDocumentation","src":"5167:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"5265:5:0","parameters":{"id":613,"nodeType":"ParameterList","parameters":[{"constant":false,"id":612,"mutability":"mutable","name":"_v","nameLocation":"5287:2:0","nodeType":"VariableDeclaration","scope":633,"src":"5271:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":611,"name":"string","nodeType":"ElementaryTypeName","src":"5271:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5270:20:0"},"returnParameters":{"id":614,"nodeType":"ParameterList","parameters":[],"src":"5305:0:0"},"scope":706,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":648,"nodeType":"FunctionDefinition","src":"5405:95:0","nodes":[],"body":{"id":647,"nodeType":"Block","src":"5449:51:0","nodes":[],"statements":[{"expression":{"id":639,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5459:9:0","subExpression":{"id":638,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5459:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":640,"nodeType":"ExpressionStatement","src":"5459:9:0"},{"expression":{"arguments":[{"id":644,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":635,"src":"5490:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":641,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5478:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":643,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5478:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":645,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5478:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":646,"nodeType":"ExpressionStatement","src":"5478:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"5414:5:0","parameters":{"id":636,"nodeType":"ParameterList","parameters":[{"constant":false,"id":635,"mutability":"mutable","name":"_v","nameLocation":"5436:2:0","nodeType":"VariableDeclaration","scope":648,"src":"5420:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":634,"name":"string","nodeType":"ElementaryTypeName","src":"5420:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5419:20:0"},"returnParameters":{"id":637,"nodeType":"ParameterList","parameters":[],"src":"5449:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":663,"nodeType":"FunctionDefinition","src":"5506:95:0","nodes":[],"body":{"id":662,"nodeType":"Block","src":"5550:51:0","nodes":[],"statements":[{"expression":{"id":654,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5560:9:0","subExpression":{"id":653,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5560:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":655,"nodeType":"ExpressionStatement","src":"5560:9:0"},{"expression":{"arguments":[{"id":659,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":650,"src":"5591:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":656,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5579:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":658,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5579:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":660,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5579:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":661,"nodeType":"ExpressionStatement","src":"5579:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"5515:5:0","parameters":{"id":651,"nodeType":"ParameterList","parameters":[{"constant":false,"id":650,"mutability":"mutable","name":"_v","nameLocation":"5537:2:0","nodeType":"VariableDeclaration","scope":663,"src":"5521:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":649,"name":"string","nodeType":"ElementaryTypeName","src":"5521:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5520:20:0"},"returnParameters":{"id":652,"nodeType":"ParameterList","parameters":[],"src":"5550:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":678,"nodeType":"FunctionDefinition","src":"5607:98:0","nodes":[],"body":{"id":677,"nodeType":"Block","src":"5653:52:0","nodes":[],"statements":[{"expression":{"id":669,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5663:9:0","subExpression":{"id":668,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5663:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":670,"nodeType":"ExpressionStatement","src":"5663:9:0"},{"expression":{"arguments":[{"id":674,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":665,"src":"5695:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":671,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5682:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":673,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":693,"src":"5682:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":675,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5682:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":676,"nodeType":"ExpressionStatement","src":"5682:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"5616:7:0","parameters":{"id":666,"nodeType":"ParameterList","parameters":[{"constant":false,"id":665,"mutability":"mutable","name":"_v","nameLocation":"5640:2:0","nodeType":"VariableDeclaration","scope":678,"src":"5624:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":664,"name":"string","nodeType":"ElementaryTypeName","src":"5624:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5623:20:0"},"returnParameters":{"id":667,"nodeType":"ParameterList","parameters":[],"src":"5653:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":693,"nodeType":"FunctionDefinition","src":"5711:97:0","nodes":[],"body":{"id":692,"nodeType":"Block","src":"5757:51:0","nodes":[],"statements":[{"expression":{"id":684,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5767:9:0","subExpression":{"id":683,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5767:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":685,"nodeType":"ExpressionStatement","src":"5767:9:0"},{"expression":{"arguments":[{"id":689,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":680,"src":"5798:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":686,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5786:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":688,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5786:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":690,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5786:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":691,"nodeType":"ExpressionStatement","src":"5786:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"5720:7:0","parameters":{"id":681,"nodeType":"ParameterList","parameters":[{"constant":false,"id":680,"mutability":"mutable","name":"_v","nameLocation":"5744:2:0","nodeType":"VariableDeclaration","scope":693,"src":"5728:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":679,"name":"string","nodeType":"ElementaryTypeName","src":"5728:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5727:20:0"},"returnParameters":{"id":682,"nodeType":"ParameterList","parameters":[],"src":"5757:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":705,"nodeType":"FunctionDefinition","src":"5814:84:0","nodes":[],"body":{"id":704,"nodeType":"Block","src":"5866:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":701,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":695,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":698,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5876:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":700,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5876:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":702,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5876:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":703,"nodeType":"ExpressionStatement","src":"5876:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"5823:8:0","parameters":{"id":696,"nodeType":"ParameterList","parameters":[{"constant":false,"id":695,"mutability":"mutable","name":"_v","nameLocation":"5848:2:0","nodeType":"VariableDeclaration","scope":705,"src":"5832:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":694,"name":"string","nodeType":"ElementaryTypeName","src":"5832:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5831:20:0"},"returnParameters":{"id":697,"nodeType":"ParameterList","parameters":[],"src":"5866:0:0"},"scope":706,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[719],"contractKind":"contract","documentation":{"id":193,"nodeType":"StructuredDocumentation","src":"2415:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[706],"name":"ScriptExample","nameLocation":"2550:13:0","scope":720,"usedErrors":[]},{"id":719,"nodeType":"ContractDefinition","src":"5902:96:0","nodes":[{"id":708,"nodeType":"VariableDeclaration","src":"5924:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"5939:3:0","scope":719,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":707,"name":"uint256","nodeType":"ElementaryTypeName","src":"5924:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":718,"nodeType":"FunctionDefinition","src":"5949:47:0","nodes":[],"body":{"id":717,"nodeType":"Block","src":"5972:24:0","nodes":[],"statements":[{"expression":{"id":715,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":713,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":708,"src":"5982:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":714,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":710,"src":"5988:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"5982:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":716,"nodeType":"ExpressionStatement","src":"5982:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":711,"nodeType":"ParameterList","parameters":[{"constant":false,"id":710,"mutability":"mutable","name":"v","nameLocation":"5969:1:0","nodeType":"VariableDeclaration","scope":718,"src":"5961:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":709,"name":"uint256","nodeType":"ElementaryTypeName","src":"5961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"5960:11:0"},"returnParameters":{"id":712,"nodeType":"ParameterList","parameters":[],"src":"5972:0:0"},"scope":719,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[719],"name":"FooBar","nameLocation":"5911:6:0","scope":720,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file +{"abi":[{"type":"function","name":"allowCheatcodes","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"broadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"broadcast","inputs":[{"name":"msgSender","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"createSelectFork","inputs":[{"name":"forkName","type":"string","internalType":"string"},{"name":"blockNumber","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"envOr","inputs":[{"name":"name","type":"string","internalType":"string"},{"name":"defaultValue","type":"bool","internalType":"bool"}],"outputs":[{"name":"value","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"etch","inputs":[{"name":"target","type":"address","internalType":"address"},{"name":"newRuntimeBytecode","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"getDeployedCode","inputs":[{"name":"artifactPath","type":"string","internalType":"string"}],"outputs":[{"name":"runtimeBytecode","type":"bytes","internalType":"bytes"}],"stateMutability":"view"},{"type":"function","name":"getNonce","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"nonce","type":"uint64","internalType":"uint64"}],"stateMutability":"view"},{"type":"function","name":"parseJsonKeys","inputs":[{"name":"json","type":"string","internalType":"string"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"keys","type":"string[]","internalType":"string[]"}],"stateMutability":"pure"},{"type":"function","name":"startBroadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"startBroadcast","inputs":[{"name":"msgSender","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"startPrank","inputs":[{"name":"msgSender","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"stopBroadcast","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"stopPrank","inputs":[],"outputs":[],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"allowCheatcodes(address)":"ea060291","broadcast()":"afc98040","broadcast(address)":"e6962cdb","createSelectFork(string,uint256)":"71ee464d","envOr(string,bool)":"4777f3cf","etch(address,bytes)":"b4d6c782","getDeployedCode(string)":"3ebf73b4","getNonce(address)":"2d0335ab","parseJsonKeys(string,string)":"213e4198","startBroadcast()":"7fb5297f","startBroadcast(address)":"7fec2a8d","startPrank(address)":"06447d56","stopBroadcast()":"76eadd36","stopPrank()":"90c5013b"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"allowCheatcodes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"broadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"msgSender\",\"type\":\"address\"}],\"name\":\"broadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"forkName\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"createSelectFork\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"defaultValue\",\"type\":\"bool\"}],\"name\":\"envOr\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"value\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"newRuntimeBytecode\",\"type\":\"bytes\"}],\"name\":\"etch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"artifactPath\",\"type\":\"string\"}],\"name\":\"getDeployedCode\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"runtimeBytecode\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"json\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"key\",\"type\":\"string\"}],\"name\":\"parseJsonKeys\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"keys\",\"type\":\"string[]\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"startBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"msgSender\",\"type\":\"address\"}],\"name\":\"startBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"msgSender\",\"type\":\"address\"}],\"name\":\"startPrank\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stopBroadcast\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stopPrank\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"Vm\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28\",\"dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"account","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"allowCheatcodes"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"broadcast"},{"inputs":[{"internalType":"address","name":"msgSender","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"broadcast"},{"inputs":[{"internalType":"string","name":"forkName","type":"string"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"createSelectFork","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"bool","name":"defaultValue","type":"bool"}],"stateMutability":"view","type":"function","name":"envOr","outputs":[{"internalType":"bool","name":"value","type":"bool"}]},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"newRuntimeBytecode","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"etch"},{"inputs":[{"internalType":"string","name":"artifactPath","type":"string"}],"stateMutability":"view","type":"function","name":"getDeployedCode","outputs":[{"internalType":"bytes","name":"runtimeBytecode","type":"bytes"}]},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"getNonce","outputs":[{"internalType":"uint64","name":"nonce","type":"uint64"}]},{"inputs":[{"internalType":"string","name":"json","type":"string"},{"internalType":"string","name":"key","type":"string"}],"stateMutability":"pure","type":"function","name":"parseJsonKeys","outputs":[{"internalType":"string[]","name":"keys","type":"string[]"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"startBroadcast"},{"inputs":[{"internalType":"address","name":"msgSender","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"startBroadcast"},{"inputs":[{"internalType":"address","name":"msgSender","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"startPrank"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"stopBroadcast"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"stopPrank"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"Vm"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da","urls":["bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28","dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":969,"exportedSymbols":{"FooBar":[799],"ForkTester":[968],"ForkedContract":[852],"NonceGetter":[833],"ScriptExample":[786],"Vm":[83],"console":[220]},"nodeType":"SourceUnit","src":"32:8375:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":83,"nodeType":"ContractDefinition","src":"120:969:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":83,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":61,"nodeType":"FunctionDefinition","src":"739:108:0","nodes":[],"functionSelector":"3ebf73b4","implemented":false,"kind":"function","modifiers":[],"name":"getDeployedCode","nameLocation":"748:15:0","parameters":{"id":57,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56,"mutability":"mutable","name":"artifactPath","nameLocation":"780:12:0","nodeType":"VariableDeclaration","scope":61,"src":"764:28:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":55,"name":"string","nodeType":"ElementaryTypeName","src":"764:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"763:30:0"},"returnParameters":{"id":60,"nodeType":"ParameterList","parameters":[{"constant":false,"id":59,"mutability":"mutable","name":"runtimeBytecode","nameLocation":"830:15:0","nodeType":"VariableDeclaration","scope":61,"src":"817:28:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":58,"name":"bytes","nodeType":"ElementaryTypeName","src":"817:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"816:30:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":68,"nodeType":"FunctionDefinition","src":"852:74:0","nodes":[],"functionSelector":"b4d6c782","implemented":false,"kind":"function","modifiers":[],"name":"etch","nameLocation":"861:4:0","parameters":{"id":66,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"target","nameLocation":"874:6:0","nodeType":"VariableDeclaration","scope":68,"src":"866:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":62,"name":"address","nodeType":"ElementaryTypeName","src":"866:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":65,"mutability":"mutable","name":"newRuntimeBytecode","nameLocation":"897:18:0","nodeType":"VariableDeclaration","scope":68,"src":"882:33:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":64,"name":"bytes","nodeType":"ElementaryTypeName","src":"882:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"865:51:0"},"returnParameters":{"id":67,"nodeType":"ParameterList","parameters":[],"src":"925:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":73,"nodeType":"FunctionDefinition","src":"931:51:0","nodes":[],"functionSelector":"ea060291","implemented":false,"kind":"function","modifiers":[],"name":"allowCheatcodes","nameLocation":"940:15:0","parameters":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"account","nameLocation":"964:7:0","nodeType":"VariableDeclaration","scope":73,"src":"956:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":69,"name":"address","nodeType":"ElementaryTypeName","src":"956:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"955:17:0"},"returnParameters":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"981:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":82,"nodeType":"FunctionDefinition","src":"987:100:0","nodes":[],"functionSelector":"71ee464d","implemented":false,"kind":"function","modifiers":[],"name":"createSelectFork","nameLocation":"996:16:0","parameters":{"id":78,"nodeType":"ParameterList","parameters":[{"constant":false,"id":75,"mutability":"mutable","name":"forkName","nameLocation":"1029:8:0","nodeType":"VariableDeclaration","scope":82,"src":"1013:24:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":74,"name":"string","nodeType":"ElementaryTypeName","src":"1013:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":77,"mutability":"mutable","name":"blockNumber","nameLocation":"1047:11:0","nodeType":"VariableDeclaration","scope":82,"src":"1039:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":76,"name":"uint256","nodeType":"ElementaryTypeName","src":"1039:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1012:47:0"},"returnParameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":82,"src":"1078:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":79,"name":"uint256","nodeType":"ElementaryTypeName","src":"1078:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1077:9:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[83],"name":"Vm","nameLocation":"130:2:0","scope":969,"usedErrors":[]},{"id":220,"nodeType":"ContractDefinition","src":"1144:1851:0","nodes":[{"id":89,"nodeType":"VariableDeclaration","src":"1166:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"1183:15:0","scope":220,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":84,"name":"address","nodeType":"ElementaryTypeName","src":"1166:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":87,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1209:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":86,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":85,"name":"address","nodeType":"ElementaryTypeName","src":"1201:7:0","typeDescriptions":{}}},"id":88,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1201:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1259:235:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1432:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1451:37:0","statements":[{"nodeType":"YulAssignment","src":"1465:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1474:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1465:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":95,"isOffset":false,"isSlot":false,"src":"1474:4:0","valueSize":1},{"declaration":102,"isOffset":false,"isSlot":false,"src":"1465:5:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1442:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"1268:25:0","parameters":{"id":96,"nodeType":"ParameterList","parameters":[{"constant":false,"id":95,"mutability":"mutable","name":"fnIn","nameLocation":"1331:4:0","nodeType":"VariableDeclaration","scope":106,"src":"1294:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":94,"nodeType":"FunctionTypeName","parameterTypes":{"id":92,"nodeType":"ParameterList","parameters":[{"constant":false,"id":91,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":94,"src":"1303:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":90,"name":"bytes","nodeType":"ElementaryTypeName","src":"1303:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1302:14:0"},"returnParameterTypes":{"id":93,"nodeType":"ParameterList","parameters":[],"src":"1331:0:0"},"src":"1294:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"1293:43:0"},"returnParameters":{"id":103,"nodeType":"ParameterList","parameters":[{"constant":false,"id":102,"mutability":"mutable","name":"fnOut","nameLocation":"1421:5:0","nodeType":"VariableDeclaration","scope":106,"src":"1384:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":101,"nodeType":"FunctionTypeName","parameterTypes":{"id":99,"nodeType":"ParameterList","parameters":[{"constant":false,"id":98,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":101,"src":"1393:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":97,"name":"bytes","nodeType":"ElementaryTypeName","src":"1393:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1392:14:0"},"returnParameterTypes":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"1421:0:0"},"src":"1384:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1383:44:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":118,"nodeType":"FunctionDefinition","src":"1500:133:0","nodes":[],"body":{"id":117,"nodeType":"Block","src":"1561:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":114,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1618:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":112,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":134,"src":"1597:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":111,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1571:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":113,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":115,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":116,"nodeType":"ExpressionStatement","src":"1571:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1509:15:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"payload","nameLocation":"1538:7:0","nodeType":"VariableDeclaration","scope":118,"src":"1525:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":107,"name":"bytes","nodeType":"ElementaryTypeName","src":"1525:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1524:22:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1561:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":134,"nodeType":"FunctionDefinition","src":"1639:380:0","nodes":[],"body":{"id":133,"nodeType":"Block","src":"1703:316:0","nodes":[],"statements":[{"assignments":[124],"declarations":[{"constant":false,"id":124,"mutability":"mutable","name":"payloadLength","nameLocation":"1721:13:0","nodeType":"VariableDeclaration","scope":133,"src":"1713:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":123,"name":"uint256","nodeType":"ElementaryTypeName","src":"1713:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":127,"initialValue":{"expression":{"id":125,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":120,"src":"1737:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":126,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1737:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1713:38:0"},{"assignments":[129],"declarations":[{"constant":false,"id":129,"mutability":"mutable","name":"consoleAddress","nameLocation":"1769:14:0","nodeType":"VariableDeclaration","scope":133,"src":"1761:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":128,"name":"address","nodeType":"ElementaryTypeName","src":"1761:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":131,"initialValue":{"id":130,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":89,"src":"1786:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1761:40:0"},{"AST":{"nodeType":"YulBlock","src":"1863:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1877:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1901:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1910:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1897:3:0"},"nodeType":"YulFunctionCall","src":"1897:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1881:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1926:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1946:3:0"},"nodeType":"YulFunctionCall","src":"1946:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1953:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1969:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1983:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1998:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"2001:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1935:10:0"},"nodeType":"YulFunctionCall","src":"1935:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1930:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":129,"isOffset":false,"isSlot":false,"src":"1953:14:0","valueSize":1},{"declaration":120,"isOffset":false,"isSlot":false,"src":"1901:7:0","valueSize":1},{"declaration":124,"isOffset":false,"isSlot":false,"src":"1983:13:0","valueSize":1}],"id":132,"nodeType":"InlineAssembly","src":"1854:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1648:19:0","parameters":{"id":121,"nodeType":"ParameterList","parameters":[{"constant":false,"id":120,"mutability":"mutable","name":"payload","nameLocation":"1681:7:0","nodeType":"VariableDeclaration","scope":134,"src":"1668:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":119,"name":"bytes","nodeType":"ElementaryTypeName","src":"1668:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1667:22:0"},"returnParameters":{"id":122,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":220,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":148,"nodeType":"FunctionDefinition","src":"2025:164:0","nodes":[],"body":{"id":147,"nodeType":"Block","src":"2070:119:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":142,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2120:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":143,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":136,"src":"2135:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":140,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2096:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":141,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2096: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":144,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2096:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":139,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2080:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":145,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2080:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":146,"nodeType":"ExpressionStatement","src":"2080:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2034:3:0","parameters":{"id":137,"nodeType":"ParameterList","parameters":[{"constant":false,"id":136,"mutability":"mutable","name":"p0","nameLocation":"2052:2:0","nodeType":"VariableDeclaration","scope":148,"src":"2038:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":135,"name":"string","nodeType":"ElementaryTypeName","src":"2038:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2037:18:0"},"returnParameters":{"id":138,"nodeType":"ParameterList","parameters":[],"src":"2070:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":165,"nodeType":"FunctionDefinition","src":"2195:182:0","nodes":[],"body":{"id":164,"nodeType":"Block","src":"2249:128:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":158,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2299:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":159,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":150,"src":"2319:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":160,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":152,"src":"2323:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":156,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2275:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":157,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2275: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":161,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2275:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":155,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2259:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":162,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2259:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":163,"nodeType":"ExpressionStatement","src":"2259:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2204:3:0","parameters":{"id":153,"nodeType":"ParameterList","parameters":[{"constant":false,"id":150,"mutability":"mutable","name":"p0","nameLocation":"2222:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2208:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":149,"name":"string","nodeType":"ElementaryTypeName","src":"2208:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":152,"mutability":"mutable","name":"p1","nameLocation":"2231:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2226:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":151,"name":"bool","nodeType":"ElementaryTypeName","src":"2226:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"2207:27:0"},"returnParameters":{"id":154,"nodeType":"ParameterList","parameters":[],"src":"2249:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":182,"nodeType":"FunctionDefinition","src":"2383:188:0","nodes":[],"body":{"id":181,"nodeType":"Block","src":"2440:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":175,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2490:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":176,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":167,"src":"2513:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":177,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":169,"src":"2517:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":173,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2466:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":174,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2466: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":178,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2466:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":172,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2450:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":179,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2450:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":180,"nodeType":"ExpressionStatement","src":"2450:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2392:3:0","parameters":{"id":170,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"mutability":"mutable","name":"p0","nameLocation":"2410:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2396:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":166,"name":"string","nodeType":"ElementaryTypeName","src":"2396:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":169,"mutability":"mutable","name":"p1","nameLocation":"2422:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2414:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":168,"name":"uint256","nodeType":"ElementaryTypeName","src":"2414:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2395:30:0"},"returnParameters":{"id":171,"nodeType":"ParameterList","parameters":[],"src":"2440:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":199,"nodeType":"FunctionDefinition","src":"2577:188:0","nodes":[],"body":{"id":198,"nodeType":"Block","src":"2634:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":192,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2684:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":193,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":184,"src":"2707:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":194,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"2711:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":190,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2660:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":191,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2660: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":195,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2660:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":189,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2644:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":196,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2644:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":197,"nodeType":"ExpressionStatement","src":"2644:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2586:3:0","parameters":{"id":187,"nodeType":"ParameterList","parameters":[{"constant":false,"id":184,"mutability":"mutable","name":"p0","nameLocation":"2604:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2590:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":183,"name":"string","nodeType":"ElementaryTypeName","src":"2590:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":186,"mutability":"mutable","name":"p1","nameLocation":"2616:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2608:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":185,"name":"address","nodeType":"ElementaryTypeName","src":"2608:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2589:30:0"},"returnParameters":{"id":188,"nodeType":"ParameterList","parameters":[],"src":"2634:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":219,"nodeType":"FunctionDefinition","src":"2771:222:0","nodes":[],"body":{"id":218,"nodeType":"Block","src":"2852:141:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":211,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2902:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":212,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":201,"src":"2931:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":213,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":203,"src":"2935:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":214,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":205,"src":"2939:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":209,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2878:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":210,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2878: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":215,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2878:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":208,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2862:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":216,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2862:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":217,"nodeType":"ExpressionStatement","src":"2862:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2780:3:0","parameters":{"id":206,"nodeType":"ParameterList","parameters":[{"constant":false,"id":201,"mutability":"mutable","name":"p0","nameLocation":"2798:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2784:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":200,"name":"string","nodeType":"ElementaryTypeName","src":"2784:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":203,"mutability":"mutable","name":"p1","nameLocation":"2816:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2802:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":202,"name":"string","nodeType":"ElementaryTypeName","src":"2802:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":205,"mutability":"mutable","name":"p2","nameLocation":"2834:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2820:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":204,"name":"string","nodeType":"ElementaryTypeName","src":"2820:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2783:54:0"},"returnParameters":{"id":207,"nodeType":"ParameterList","parameters":[],"src":"2852:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[220],"name":"console","nameLocation":"1152:7:0","scope":969,"usedErrors":[]},{"id":786,"nodeType":"ContractDefinition","src":"3123:3912:0","nodes":[{"id":235,"nodeType":"VariableDeclaration","src":"3152:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"3178:10:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":222,"name":"address","nodeType":"ElementaryTypeName","src":"3152:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3225:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":229,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"3215:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":231,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3215:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":228,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3207:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":227,"name":"uint256","nodeType":"ElementaryTypeName","src":"3207:7:0","typeDescriptions":{}}},"id":232,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3207:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":226,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3199:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":225,"name":"uint160","nodeType":"ElementaryTypeName","src":"3199:7:0","typeDescriptions":{}}},"id":233,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3199:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":224,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3191:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":223,"name":"address","nodeType":"ElementaryTypeName","src":"3191:7:0","typeDescriptions":{}}},"id":234,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3191:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":241,"nodeType":"VariableDeclaration","src":"3252:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"3273:2:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":237,"nodeType":"UserDefinedTypeName","pathNode":{"id":236,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"3252:2:0"},"referencedDeclaration":83,"src":"3252:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":239,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":235,"src":"3281:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":238,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"3278:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":240,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3278:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":243,"nodeType":"VariableDeclaration","src":"3356:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"3371:7:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":242,"name":"uint256","nodeType":"ElementaryTypeName","src":"3356:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":456,"nodeType":"FunctionDefinition","src":"3468:1428:0","nodes":[],"body":{"id":455,"nodeType":"Block","src":"3490:1406:0","nodes":[],"statements":[{"assignments":[248],"declarations":[{"constant":false,"id":248,"mutability":"mutable","name":"x","nameLocation":"3505:1:0","nodeType":"VariableDeclaration","scope":455,"src":"3500:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":247,"name":"bool","nodeType":"ElementaryTypeName","src":"3500:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":254,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":251,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3518:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":252,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"3534:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":249,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3509:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":250,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"3509:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"3500:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":258,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3562:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":259,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":248,"src":"3585:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":255,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3550:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":257,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":165,"src":"3550:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":260,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3550:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":261,"nodeType":"ExpressionStatement","src":"3550:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":265,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3610:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":268,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3635:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":267,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3627:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":266,"name":"address","nodeType":"ElementaryTypeName","src":"3627:7:0","typeDescriptions":{}}},"id":269,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3627:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":262,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3598:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3598:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3598:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":271,"nodeType":"ExpressionStatement","src":"3598:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":275,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3663:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":280,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3701:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":279,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3693:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":278,"name":"address","nodeType":"ElementaryTypeName","src":"3693:7:0","typeDescriptions":{}}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3693:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":276,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3681:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3681:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":282,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":272,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3651:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":274,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3651:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":283,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3651:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":284,"nodeType":"ExpressionStatement","src":"3651:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":288,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3730:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":291,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3753:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3753:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":290,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3745:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":289,"name":"address","nodeType":"ElementaryTypeName","src":"3745:7:0","typeDescriptions":{}}},"id":293,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3745:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":285,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3718:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3718:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3718:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":295,"nodeType":"ExpressionStatement","src":"3718:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":299,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3787:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":304,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3823:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":305,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3823:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":303,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3815:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":302,"name":"address","nodeType":"ElementaryTypeName","src":"3815:7:0","typeDescriptions":{}}},"id":306,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3815:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":300,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3803:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3803:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3803:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":296,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3775:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":298,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3775:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":308,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3775:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":309,"nodeType":"ExpressionStatement","src":"3775:61:0"},{"assignments":[311],"declarations":[{"constant":false,"id":311,"mutability":"mutable","name":"json","nameLocation":"3861:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3847:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":310,"name":"string","nodeType":"ElementaryTypeName","src":"3847:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":313,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3868:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3847:55:0"},{"assignments":[318],"declarations":[{"constant":false,"id":318,"mutability":"mutable","name":"keys","nameLocation":"3928:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3912:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":316,"name":"string","nodeType":"ElementaryTypeName","src":"3912:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":317,"nodeType":"ArrayTypeName","src":"3912:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":324,"initialValue":{"arguments":[{"id":321,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":311,"src":"3952:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3958:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":319,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3935:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":320,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3935:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":323,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3935:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3912:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":328,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3995:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":329,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4003:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":331,"indexExpression":{"hexValue":"30","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4008:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4003:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":332,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4012:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":334,"indexExpression":{"hexValue":"31","id":333,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4017:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4012:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":325,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3983:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":327,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":219,"src":"3983:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3983:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":336,"nodeType":"ExpressionStatement","src":"3983:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":340,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4042:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":337,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4031:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":339,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4031:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4031:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":342,"nodeType":"ExpressionStatement","src":"4031:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":350,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4098:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4090:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":348,"name":"uint160","nodeType":"ElementaryTypeName","src":"4090:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4090:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":347,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4082:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":346,"name":"address","nodeType":"ElementaryTypeName","src":"4082:7:0","typeDescriptions":{}}},"id":352,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4082:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":343,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4068:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":345,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"4068:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":353,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4068:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":354,"nodeType":"ExpressionStatement","src":"4068:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":358,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4126:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":355,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4115:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":357,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4115:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":359,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4115:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":360,"nodeType":"ExpressionStatement","src":"4115:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":364,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4163:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":367,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4198:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":368,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"4198:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":366,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4190:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":365,"name":"address","nodeType":"ElementaryTypeName","src":"4190:7:0","typeDescriptions":{}}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4190:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":361,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4151:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4151:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":370,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4151:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":371,"nodeType":"ExpressionStatement","src":"4151:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":375,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4232:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":378,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4270:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":377,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4262:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":376,"name":"address","nodeType":"ElementaryTypeName","src":"4262:7:0","typeDescriptions":{}}},"id":379,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4262:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":372,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4220:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":374,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4220:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":380,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4220:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":381,"nodeType":"ExpressionStatement","src":"4220:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4297:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":382,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4286:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4286:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":386,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4286:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":387,"nodeType":"ExpressionStatement","src":"4286:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4322:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":390,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"4322:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":391,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4322:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":392,"nodeType":"ExpressionStatement","src":"4322:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":396,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4357:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":393,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4346:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4346:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":397,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4346:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":398,"nodeType":"ExpressionStatement","src":"4346:33:0"},{"assignments":[400],"declarations":[{"constant":false,"id":400,"mutability":"mutable","name":"tmpNonceGetter","nameLocation":"4480:14:0","nodeType":"VariableDeclaration","scope":455,"src":"4472:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":399,"name":"address","nodeType":"ElementaryTypeName","src":"4472:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":413,"initialValue":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"74656d70206e6f6e6365207465737420676574746572","id":408,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4531:24:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""},"value":"temp nonce test getter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""}],"id":407,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"4521:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":409,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4521:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":406,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4513:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":405,"name":"uint256","nodeType":"ElementaryTypeName","src":"4513:7:0","typeDescriptions":{}}},"id":410,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4513:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":404,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4505:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":403,"name":"uint160","nodeType":"ElementaryTypeName","src":"4505:7:0","typeDescriptions":{}}},"id":411,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4505:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":402,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4497:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":401,"name":"address","nodeType":"ElementaryTypeName","src":"4497:7:0","typeDescriptions":{}}},"id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4497:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"4472:87:0"},{"expression":{"arguments":[{"id":417,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4577:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"hexValue":"5363726970744578616d706c652e732e736f6c3a4e6f6e6365476574746572","id":420,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4612:33:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""},"value":"ScriptExample.s.sol:NonceGetter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""}],"expression":{"id":418,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4593:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getDeployedCode","nodeType":"MemberAccess","referencedDeclaration":61,"src":"4593:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) view external returns (bytes memory)"}},"id":421,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4593:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":414,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4569:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":416,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"etch","nodeType":"MemberAccess","referencedDeclaration":68,"src":"4569:7:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory) external"}},"id":422,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4569:78:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":423,"nodeType":"ExpressionStatement","src":"4569:78:0"},{"expression":{"arguments":[{"id":427,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4676:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":424,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4657:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":426,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"allowCheatcodes","nodeType":"MemberAccess","referencedDeclaration":73,"src":"4657:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":428,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":429,"nodeType":"ExpressionStatement","src":"4657:34:0"},{"assignments":[431],"declarations":[{"constant":false,"id":431,"mutability":"mutable","name":"v","nameLocation":"4709:1:0","nodeType":"VariableDeclaration","scope":455,"src":"4701:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":430,"name":"uint256","nodeType":"ElementaryTypeName","src":"4701:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":441,"initialValue":{"arguments":[{"arguments":[{"id":438,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4758:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":437,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4750:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":436,"name":"address","nodeType":"ElementaryTypeName","src":"4750:7:0","typeDescriptions":{}}},"id":439,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4750:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"arguments":[{"id":433,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4725:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":432,"name":"NonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":833,"src":"4713:11:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_NonceGetter_$833_$","typeString":"type(contract NonceGetter)"}},"id":434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_NonceGetter_$833","typeString":"contract NonceGetter"}},"id":435,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":832,"src":"4713:36:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view external returns (uint256)"}},"id":440,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"4701:63:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683a","id":445,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4786:68:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},"value":"nonce from nonce getter, no explicit access required with vm.etch:"},{"id":446,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":431,"src":"4856:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":442,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4774:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":444,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"4774:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4774:84:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":448,"nodeType":"ExpressionStatement","src":"4774:84:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":452,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4881:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":449,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4869:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":451,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"4869:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4869:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":454,"nodeType":"ExpressionStatement","src":"4869:20:0"}]},"documentation":{"id":244,"nodeType":"StructuredDocumentation","src":"3385:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"3477:3:0","parameters":{"id":245,"nodeType":"ParameterList","parameters":[],"src":"3480:2:0"},"returnParameters":{"id":246,"nodeType":"ParameterList","parameters":[],"src":"3490:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":689,"nodeType":"FunctionDefinition","src":"4963:1333:0","nodes":[],"body":{"id":688,"nodeType":"Block","src":"4994:1302:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":463,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5016:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":470,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5059:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":469,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5051:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":468,"name":"address","nodeType":"ElementaryTypeName","src":"5051:7:0","typeDescriptions":{}}},"id":471,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5051:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":466,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5039:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":467,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5039:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5039:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":465,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5031:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":464,"name":"uint256","nodeType":"ElementaryTypeName","src":"5031:7:0","typeDescriptions":{}}},"id":473,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5031:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":460,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5004:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":462,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"5004:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":474,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5004:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":475,"nodeType":"ExpressionStatement","src":"5004:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":479,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5090:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":476,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5078:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":478,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5078:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":480,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5078:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":481,"nodeType":"ExpressionStatement","src":"5078:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":482,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5117:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5117:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":485,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5117:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":486,"nodeType":"ExpressionStatement","src":"5117:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":490,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5152:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":487,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5141:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":489,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5141:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":491,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5141:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":492,"nodeType":"ExpressionStatement","src":"5141:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":496,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5188:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":493,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5177:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5177:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":497,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5177:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":498,"nodeType":"ExpressionStatement","src":"5177:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":502,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5226:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":499,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5214:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5214:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":503,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5214:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":504,"nodeType":"ExpressionStatement","src":"5214:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5291:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":511,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5283:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":510,"name":"uint160","nodeType":"ElementaryTypeName","src":"5283:7:0","typeDescriptions":{}}},"id":513,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5283:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5275:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":508,"name":"address","nodeType":"ElementaryTypeName","src":"5275:7:0","typeDescriptions":{}}},"id":514,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5275:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":505,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5257:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":507,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5257:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":515,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5257:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":516,"nodeType":"ExpressionStatement","src":"5257:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":520,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5323:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":517,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5312:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":519,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5312:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":521,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5312:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":522,"nodeType":"ExpressionStatement","src":"5312:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":526,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5362:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":523,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5351:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":525,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5351:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5351:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":528,"nodeType":"ExpressionStatement","src":"5351:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":532,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5404:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":529,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5390:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":531,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":785,"src":"5390:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":533,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5390:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":534,"nodeType":"ExpressionStatement","src":"5390:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":535,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5431:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":537,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5431:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":538,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5431:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":539,"nodeType":"ExpressionStatement","src":"5431:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":543,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5470:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":540,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5459:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":542,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5459:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5459:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":545,"nodeType":"ExpressionStatement","src":"5459:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":549,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5511:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":546,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5499:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5499:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":550,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5499:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":551,"nodeType":"ExpressionStatement","src":"5499:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":559,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5572:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":558,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5564:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":557,"name":"uint160","nodeType":"ElementaryTypeName","src":"5564:7:0","typeDescriptions":{}}},"id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5564:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":556,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5556:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":555,"name":"address","nodeType":"ElementaryTypeName","src":"5556:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5556:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":552,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5538:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":554,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5538:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5538:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":563,"nodeType":"ExpressionStatement","src":"5538:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":567,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5604:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":564,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5591:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":758,"src":"5591:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":568,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5591:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":569,"nodeType":"ExpressionStatement","src":"5591:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":570,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5623:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5623:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5623:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"5623:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5664:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5652:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5652:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5652:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"5652:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":588,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5725:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":587,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5717:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":586,"name":"uint160","nodeType":"ElementaryTypeName","src":"5717:7:0","typeDescriptions":{}}},"id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5717:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":585,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5709:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":584,"name":"address","nodeType":"ElementaryTypeName","src":"5709:7:0","typeDescriptions":{}}},"id":590,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5709:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5696:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5696:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":591,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5696:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":592,"nodeType":"ExpressionStatement","src":"5696:40:0"},{"assignments":[595],"declarations":[{"constant":false,"id":595,"mutability":"mutable","name":"x","nameLocation":"5753:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5746:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":594,"nodeType":"UserDefinedTypeName","pathNode":{"id":593,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5746:6:0"},"referencedDeclaration":799,"src":"5746:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":601,"initialValue":{"arguments":[{"hexValue":"31323334","id":599,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5768:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":598,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5757:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":597,"nodeType":"UserDefinedTypeName","pathNode":{"id":596,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5761:6:0"},"referencedDeclaration":799,"src":"5761:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":600,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5757:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5746:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":607,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":603,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":595,"src":"5791:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"5791:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5791:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":606,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5802:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"5791:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323334","id":608,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5808:35:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""},"value":"FooBar: foo in create is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""}],"id":602,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"5783:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":609,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5783:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":610,"nodeType":"ExpressionStatement","src":"5783:61:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":614,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5867:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":611,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5855:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":613,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5855:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":615,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5855:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":616,"nodeType":"ExpressionStatement","src":"5855:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5917:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":623,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5909:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":622,"name":"uint160","nodeType":"ElementaryTypeName","src":"5909:7:0","typeDescriptions":{}}},"id":625,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5909:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":621,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5901:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":620,"name":"address","nodeType":"ElementaryTypeName","src":"5901:7:0","typeDescriptions":{}}},"id":626,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5901:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":617,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5888:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":627,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5888:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":628,"nodeType":"ExpressionStatement","src":"5888:38:0"},{"assignments":[631],"declarations":[{"constant":false,"id":631,"mutability":"mutable","name":"y","nameLocation":"5943:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5936:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":630,"nodeType":"UserDefinedTypeName","pathNode":{"id":629,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5936:6:0"},"referencedDeclaration":799,"src":"5936:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":645,"initialValue":{"arguments":[{"hexValue":"31323334","id":643,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5986:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":634,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5947:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":633,"nodeType":"UserDefinedTypeName","pathNode":{"id":632,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5951:6:0"},"referencedDeclaration":799,"src":"5951:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":642,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":639,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5980:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":638,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5972:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":637,"name":"uint256","nodeType":"ElementaryTypeName","src":"5972:7:0","typeDescriptions":{}}},"id":640,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5972:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":636,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5964:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":635,"name":"bytes32","nodeType":"ElementaryTypeName","src":"5964:7:0","typeDescriptions":{}}},"id":641,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5964:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"5947:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":644,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5947:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5936:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":651,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":647,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":631,"src":"6009:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":648,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"6009:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":649,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6009:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":650,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6020:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"6009:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e2063726561746532206973206e6f742031323334","id":652,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6026:36:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""},"value":"FooBar: foo in create2 is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""}],"id":646,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"6001:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6001:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":654,"nodeType":"ExpressionStatement","src":"6001:62:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":658,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6085:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":655,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6073:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":657,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6073:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":659,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6073:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":660,"nodeType":"ExpressionStatement","src":"6073:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":661,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6177:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"6177:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":664,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6177:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":665,"nodeType":"ExpressionStatement","src":"6177:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":669,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6212:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":668,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"6201:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":667,"nodeType":"UserDefinedTypeName","pathNode":{"id":666,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"6205:6:0"},"referencedDeclaration":799,"src":"6205:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":670,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6201:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":671,"nodeType":"ExpressionStatement","src":"6201:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":675,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6240:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":682,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6281:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":681,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6273:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":680,"name":"address","nodeType":"ElementaryTypeName","src":"6273:7:0","typeDescriptions":{}}},"id":683,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6273:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":678,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6261:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"6261:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":684,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6261:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":677,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6253:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":676,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:0","typeDescriptions":{}}},"id":685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6253:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":672,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6228:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":674,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"6228:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":686,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6228:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":687,"nodeType":"ExpressionStatement","src":"6228:61:0"}]},"documentation":{"id":457,"nodeType":"StructuredDocumentation","src":"4902:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"4972:12:0","parameters":{"id":458,"nodeType":"ParameterList","parameters":[],"src":"4984:2:0"},"returnParameters":{"id":459,"nodeType":"ParameterList","parameters":[],"src":"4994:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":713,"nodeType":"FunctionDefinition","src":"6391:143:0","nodes":[],"body":{"id":712,"nodeType":"Block","src":"6440:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":698,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":692,"src":"6462:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":695,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6450:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":697,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6450:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":699,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6450:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":700,"nodeType":"ExpressionStatement","src":"6450:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6487:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":707,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"6515:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":708,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"6515:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":706,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6507:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":705,"name":"address","nodeType":"ElementaryTypeName","src":"6507:7:0","typeDescriptions":{}}},"id":709,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6507:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":701,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6475:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"6475:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":710,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6475:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":711,"nodeType":"ExpressionStatement","src":"6475:52:0"}]},"documentation":{"id":690,"nodeType":"StructuredDocumentation","src":"6302:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"6400:5:0","parameters":{"id":693,"nodeType":"ParameterList","parameters":[{"constant":false,"id":692,"mutability":"mutable","name":"_v","nameLocation":"6422:2:0","nodeType":"VariableDeclaration","scope":713,"src":"6406:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":691,"name":"string","nodeType":"ElementaryTypeName","src":"6406:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6405:20:0"},"returnParameters":{"id":694,"nodeType":"ParameterList","parameters":[],"src":"6440:0:0"},"scope":786,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":728,"nodeType":"FunctionDefinition","src":"6540:95:0","nodes":[],"body":{"id":727,"nodeType":"Block","src":"6584:51:0","nodes":[],"statements":[{"expression":{"id":719,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6594:9:0","subExpression":{"id":718,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6594:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":720,"nodeType":"ExpressionStatement","src":"6594:9:0"},{"expression":{"arguments":[{"id":724,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":715,"src":"6625:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":721,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6613:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":723,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6613:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":725,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6613:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":726,"nodeType":"ExpressionStatement","src":"6613:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"6549:5:0","parameters":{"id":716,"nodeType":"ParameterList","parameters":[{"constant":false,"id":715,"mutability":"mutable","name":"_v","nameLocation":"6571:2:0","nodeType":"VariableDeclaration","scope":728,"src":"6555:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":714,"name":"string","nodeType":"ElementaryTypeName","src":"6555:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6554:20:0"},"returnParameters":{"id":717,"nodeType":"ParameterList","parameters":[],"src":"6584:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":743,"nodeType":"FunctionDefinition","src":"6641:95:0","nodes":[],"body":{"id":742,"nodeType":"Block","src":"6685:51:0","nodes":[],"statements":[{"expression":{"id":734,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6695:9:0","subExpression":{"id":733,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6695:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":735,"nodeType":"ExpressionStatement","src":"6695:9:0"},{"expression":{"arguments":[{"id":739,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":730,"src":"6726:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":736,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6714:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":738,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6714:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":740,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6714:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":741,"nodeType":"ExpressionStatement","src":"6714:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"6650:5:0","parameters":{"id":731,"nodeType":"ParameterList","parameters":[{"constant":false,"id":730,"mutability":"mutable","name":"_v","nameLocation":"6672:2:0","nodeType":"VariableDeclaration","scope":743,"src":"6656:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":729,"name":"string","nodeType":"ElementaryTypeName","src":"6656:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6655:20:0"},"returnParameters":{"id":732,"nodeType":"ParameterList","parameters":[],"src":"6685:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":758,"nodeType":"FunctionDefinition","src":"6742:98:0","nodes":[],"body":{"id":757,"nodeType":"Block","src":"6788:52:0","nodes":[],"statements":[{"expression":{"id":749,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6798:9:0","subExpression":{"id":748,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6798:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":750,"nodeType":"ExpressionStatement","src":"6798:9:0"},{"expression":{"arguments":[{"id":754,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":745,"src":"6830:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":751,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6817:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":753,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":773,"src":"6817:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":755,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6817:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":756,"nodeType":"ExpressionStatement","src":"6817:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"6751:7:0","parameters":{"id":746,"nodeType":"ParameterList","parameters":[{"constant":false,"id":745,"mutability":"mutable","name":"_v","nameLocation":"6775:2:0","nodeType":"VariableDeclaration","scope":758,"src":"6759:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":744,"name":"string","nodeType":"ElementaryTypeName","src":"6759:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6758:20:0"},"returnParameters":{"id":747,"nodeType":"ParameterList","parameters":[],"src":"6788:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":773,"nodeType":"FunctionDefinition","src":"6846:97:0","nodes":[],"body":{"id":772,"nodeType":"Block","src":"6892:51:0","nodes":[],"statements":[{"expression":{"id":764,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6902:9:0","subExpression":{"id":763,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6902:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":765,"nodeType":"ExpressionStatement","src":"6902:9:0"},{"expression":{"arguments":[{"id":769,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":760,"src":"6933:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":766,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6921:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":768,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6921:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":770,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6921:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":771,"nodeType":"ExpressionStatement","src":"6921:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"6855:7:0","parameters":{"id":761,"nodeType":"ParameterList","parameters":[{"constant":false,"id":760,"mutability":"mutable","name":"_v","nameLocation":"6879:2:0","nodeType":"VariableDeclaration","scope":773,"src":"6863:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":759,"name":"string","nodeType":"ElementaryTypeName","src":"6863:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6862:20:0"},"returnParameters":{"id":762,"nodeType":"ParameterList","parameters":[],"src":"6892:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":785,"nodeType":"FunctionDefinition","src":"6949:84:0","nodes":[],"body":{"id":784,"nodeType":"Block","src":"7001:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":781,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":775,"src":"7023:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":778,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"7011:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":780,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"7011:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":782,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7011:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":783,"nodeType":"ExpressionStatement","src":"7011:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"6958:8:0","parameters":{"id":776,"nodeType":"ParameterList","parameters":[{"constant":false,"id":775,"mutability":"mutable","name":"_v","nameLocation":"6983:2:0","nodeType":"VariableDeclaration","scope":785,"src":"6967:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":774,"name":"string","nodeType":"ElementaryTypeName","src":"6967:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6966:20:0"},"returnParameters":{"id":777,"nodeType":"ParameterList","parameters":[],"src":"7001:0:0"},"scope":786,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[799],"contractKind":"contract","documentation":{"id":221,"nodeType":"StructuredDocumentation","src":"2997:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[786],"name":"ScriptExample","nameLocation":"3132:13:0","scope":969,"usedErrors":[]},{"id":799,"nodeType":"ContractDefinition","src":"7037:96:0","nodes":[{"id":788,"nodeType":"VariableDeclaration","src":"7059:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"7074:3:0","scope":799,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":787,"name":"uint256","nodeType":"ElementaryTypeName","src":"7059:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":798,"nodeType":"FunctionDefinition","src":"7084:47:0","nodes":[],"body":{"id":797,"nodeType":"Block","src":"7107:24:0","nodes":[],"statements":[{"expression":{"id":795,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":793,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":788,"src":"7117:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":794,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":790,"src":"7123:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"7117:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":796,"nodeType":"ExpressionStatement","src":"7117:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":791,"nodeType":"ParameterList","parameters":[{"constant":false,"id":790,"mutability":"mutable","name":"v","nameLocation":"7104:1:0","nodeType":"VariableDeclaration","scope":798,"src":"7096:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":789,"name":"uint256","nodeType":"ElementaryTypeName","src":"7096:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7095:11:0"},"returnParameters":{"id":792,"nodeType":"ParameterList","parameters":[],"src":"7107:0:0"},"scope":799,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[799],"name":"FooBar","nameLocation":"7046:6:0","scope":969,"usedErrors":[]},{"id":833,"nodeType":"ContractDefinition","src":"7135:281:0","nodes":[{"id":813,"nodeType":"VariableDeclaration","src":"7162:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7188:10:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":800,"name":"address","nodeType":"ElementaryTypeName","src":"7162:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":808,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7235:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":807,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7225:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":809,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7225:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":806,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7217:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":805,"name":"uint256","nodeType":"ElementaryTypeName","src":"7217:7:0","typeDescriptions":{}}},"id":810,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7217:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":804,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7209:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":803,"name":"uint160","nodeType":"ElementaryTypeName","src":"7209:7:0","typeDescriptions":{}}},"id":811,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7209:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":802,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":801,"name":"address","nodeType":"ElementaryTypeName","src":"7201:7:0","typeDescriptions":{}}},"id":812,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7201:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":819,"nodeType":"VariableDeclaration","src":"7262:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7283:2:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":815,"nodeType":"UserDefinedTypeName","pathNode":{"id":814,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7262:2:0"},"referencedDeclaration":83,"src":"7262:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":817,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":813,"src":"7291:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":816,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7288:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":818,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7288:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":832,"nodeType":"FunctionDefinition","src":"7309:105:0","nodes":[],"body":{"id":831,"nodeType":"Block","src":"7372:42:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":828,"name":"_addr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":821,"src":"7401:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":826,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":819,"src":"7389:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":827,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7389:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":829,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7389:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"functionReturnParameters":825,"id":830,"nodeType":"Return","src":"7382:25:0"}]},"functionSelector":"2d0335ab","implemented":true,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"7318:8:0","parameters":{"id":822,"nodeType":"ParameterList","parameters":[{"constant":false,"id":821,"mutability":"mutable","name":"_addr","nameLocation":"7335:5:0","nodeType":"VariableDeclaration","scope":832,"src":"7327:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":820,"name":"address","nodeType":"ElementaryTypeName","src":"7327:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7326:15:0"},"returnParameters":{"id":825,"nodeType":"ParameterList","parameters":[{"constant":false,"id":824,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":832,"src":"7363:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":823,"name":"uint256","nodeType":"ElementaryTypeName","src":"7363:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7362:9:0"},"scope":833,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"NonceGetter","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[833],"name":"NonceGetter","nameLocation":"7144:11:0","scope":969,"usedErrors":[]},{"id":852,"nodeType":"ContractDefinition","src":"7418:174:0","nodes":[{"id":835,"nodeType":"VariableDeclaration","src":"7448:18:0","nodes":[],"constant":false,"mutability":"mutable","name":"v","nameLocation":"7465:1:0","scope":852,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":834,"name":"uint256","nodeType":"ElementaryTypeName","src":"7448:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"id":843,"nodeType":"FunctionDefinition","src":"7473:36:0","nodes":[],"body":{"id":842,"nodeType":"Block","src":"7487:22:0","nodes":[],"statements":[{"expression":{"id":840,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":838,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7497:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"31","id":839,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7501:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"7497:5:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":841,"nodeType":"ExpressionStatement","src":"7497:5:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":836,"nodeType":"ParameterList","parameters":[],"src":"7484:2:0"},"returnParameters":{"id":837,"nodeType":"ParameterList","parameters":[],"src":"7487:0:0"},"scope":852,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":851,"nodeType":"FunctionDefinition","src":"7515:75:0","nodes":[],"body":{"id":850,"nodeType":"Block","src":"7565:25:0","nodes":[],"statements":[{"expression":{"id":848,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7582:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":847,"id":849,"nodeType":"Return","src":"7575:8:0"}]},"functionSelector":"20965255","implemented":true,"kind":"function","modifiers":[],"name":"getValue","nameLocation":"7524:8:0","parameters":{"id":844,"nodeType":"ParameterList","parameters":[],"src":"7532:2:0"},"returnParameters":{"id":847,"nodeType":"ParameterList","parameters":[{"constant":false,"id":846,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":851,"src":"7556:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":845,"name":"uint256","nodeType":"ElementaryTypeName","src":"7556:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7555:9:0"},"scope":852,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkedContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[852],"name":"ForkedContract","nameLocation":"7427:14:0","scope":969,"usedErrors":[]},{"id":968,"nodeType":"ContractDefinition","src":"7594:813:0","nodes":[{"id":866,"nodeType":"VariableDeclaration","src":"7620:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7646:10:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":853,"name":"address","nodeType":"ElementaryTypeName","src":"7620:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":861,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7693:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":860,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7683:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":862,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7683:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":859,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7675:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":858,"name":"uint256","nodeType":"ElementaryTypeName","src":"7675:7:0","typeDescriptions":{}}},"id":863,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7675:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":857,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7667:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":856,"name":"uint160","nodeType":"ElementaryTypeName","src":"7667:7:0","typeDescriptions":{}}},"id":864,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7667:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":855,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7659:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":854,"name":"address","nodeType":"ElementaryTypeName","src":"7659:7:0","typeDescriptions":{}}},"id":865,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7659:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":872,"nodeType":"VariableDeclaration","src":"7720:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7741:2:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":868,"nodeType":"UserDefinedTypeName","pathNode":{"id":867,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7720:2:0"},"referencedDeclaration":83,"src":"7720:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":870,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":866,"src":"7749:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":869,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7746:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":871,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7746:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":967,"nodeType":"FunctionDefinition","src":"7767:638:0","nodes":[],"body":{"id":966,"nodeType":"Block","src":"7791:614:0","nodes":[],"statements":[{"assignments":[876],"declarations":[{"constant":false,"id":876,"mutability":"mutable","name":"testAddr","nameLocation":"7809:8:0","nodeType":"VariableDeclaration","scope":966,"src":"7801:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":875,"name":"address","nodeType":"ElementaryTypeName","src":"7801:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":884,"initialValue":{"arguments":[{"arguments":[{"hexValue":"307831323334","id":881,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7836:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":880,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7828:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":879,"name":"uint160","nodeType":"ElementaryTypeName","src":"7828:7:0","typeDescriptions":{}}},"id":882,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7828:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":878,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7820:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":877,"name":"address","nodeType":"ElementaryTypeName","src":"7820:7:0","typeDescriptions":{}}},"id":883,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7820:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"7801:43:0"},{"assignments":[887],"declarations":[{"constant":false,"id":887,"mutability":"mutable","name":"fc","nameLocation":"7869:2:0","nodeType":"VariableDeclaration","scope":966,"src":"7854:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"},"typeName":{"id":886,"nodeType":"UserDefinedTypeName","pathNode":{"id":885,"name":"ForkedContract","nodeType":"IdentifierPath","referencedDeclaration":852,"src":"7854:14:0"},"referencedDeclaration":852,"src":"7854:14:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"visibility":"internal"}],"id":891,"initialValue":{"arguments":[{"id":889,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7889:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":888,"name":"ForkedContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":852,"src":"7874:14:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ForkedContract_$852_$","typeString":"type(contract ForkedContract)"}},"id":890,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7874:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"nodeType":"VariableDeclarationStatement","src":"7854:44:0"},{"expression":{"arguments":[{"hexValue":"666f726b31","id":895,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7929:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},"value":"fork1"},{"hexValue":"3132333435","id":896,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7938:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"}],"expression":{"id":892,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7909:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":894,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"7909:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":897,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7909:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":898,"nodeType":"ExpressionStatement","src":"7909:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":905,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":902,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7974:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":900,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7962:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":901,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7962:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":903,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7962:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3132333435","id":904,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7987:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"},"src":"7962:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":906,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7994:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":899,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"7954:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":907,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7954:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":908,"nodeType":"ExpressionStatement","src":"7954:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":914,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":910,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8036:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":911,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8036:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":912,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8036:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31","id":913,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8053:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"8036:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652031","id":915,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8056:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""},"value":"value should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""}],"id":909,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8028:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":916,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8028:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":917,"nodeType":"ExpressionStatement","src":"8028:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":925,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":919,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8094:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":920,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8094:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"31","id":923,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8122:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"}],"id":922,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8114:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":921,"name":"uint256","nodeType":"ElementaryTypeName","src":"8114:7:0","typeDescriptions":{}}},"id":924,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8114:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8094:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652031","id":926,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8126:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""},"value":"balance should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""}],"id":918,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8086:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":927,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8086:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":928,"nodeType":"ExpressionStatement","src":"8086:62:0"},{"expression":{"arguments":[{"hexValue":"666f726b32","id":932,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8179:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},"value":"fork2"},{"hexValue":"3233343536","id":933,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8188:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"}],"expression":{"id":929,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8159:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":931,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"8159:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":934,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8159:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":935,"nodeType":"ExpressionStatement","src":"8159:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":939,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8224:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":937,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8212:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":938,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"8212:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":940,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8212:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3233343536","id":941,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8237:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"},"src":"8212:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":943,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8244:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":936,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8204:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":944,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8204:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":945,"nodeType":"ExpressionStatement","src":"8204:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":951,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":947,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8286:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":948,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8286:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":949,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8286:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"32","id":950,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8303:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"src":"8286:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652032","id":952,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8306:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""},"value":"value should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""}],"id":946,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8278:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":953,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8278:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":954,"nodeType":"ExpressionStatement","src":"8278:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":962,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":956,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8344:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":957,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8344:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"32","id":960,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8372:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"}],"id":959,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8364:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":958,"name":"uint256","nodeType":"ElementaryTypeName","src":"8364:7:0","typeDescriptions":{}}},"id":961,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8364:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8344:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652032","id":963,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8376:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""},"value":"balance should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""}],"id":955,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8336:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":964,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8336:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":965,"nodeType":"ExpressionStatement","src":"8336:62:0"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"7776:3:0","parameters":{"id":873,"nodeType":"ParameterList","parameters":[],"src":"7779:2:0"},"returnParameters":{"id":874,"nodeType":"ParameterList","parameters":[],"src":"7791:0:0"},"scope":968,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkTester","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[968],"name":"ForkTester","nameLocation":"7603:10:0","scope":969,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/console.json b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/console.json index 390e798a47011..6ef800dd39d51 100644 --- a/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/console.json +++ b/op-chain-ops/script/testdata/test-artifacts/ScriptExample.s.sol/console.json @@ -1 +1 @@ -{"abi":[],"bytecode":{"object":"0x602d6037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c634300080f000a","sourceMap":"791:1622:0:-:0;;;;;;;;;;;;;;;-1:-1:-1;;;791:1622:0;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c634300080f000a","sourceMap":"791:1622:0:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"console\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3\",\"dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"console"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x8d1dfa41908e7ccc3a498a2a2aa51c5275bedbb904ce32d08f8598e36f896d8d","urls":["bzz-raw://5117bb7158363cae8b9dc0508d2852692fd36172f1c699ff680afbb5acebe1f3","dweb:/ipfs/QmQdahJ8SPKfJ4yea5Ge9qaj5qh1TxVffhHvaWytBaL95h"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":720,"exportedSymbols":{"FooBar":[719],"ScriptExample":[706],"Vm":[55],"console":[192]},"nodeType":"SourceUnit","src":"32:5967:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":55,"nodeType":"ContractDefinition","src":"120:616:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":55,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":55,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":55,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[55],"name":"Vm","nameLocation":"130:2:0","scope":720,"usedErrors":[]},{"id":192,"nodeType":"ContractDefinition","src":"791:1622:0","nodes":[{"id":61,"nodeType":"VariableDeclaration","src":"813:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"830:15:0","scope":192,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":56,"name":"address","nodeType":"ElementaryTypeName","src":"813:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":59,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"856:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":58,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"848:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":57,"name":"address","nodeType":"ElementaryTypeName","src":"848:7:0","typeDescriptions":{}}},"id":60,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"848:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":78,"nodeType":"FunctionDefinition","src":"906:221:0","nodes":[],"body":{"id":77,"nodeType":"Block","src":"1065:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1084:37:0","statements":[{"nodeType":"YulAssignment","src":"1098:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1107:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1098:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":67,"isOffset":false,"isSlot":false,"src":"1107:4:0","valueSize":1},{"declaration":74,"isOffset":false,"isSlot":false,"src":"1098:5:0","valueSize":1}],"id":76,"nodeType":"InlineAssembly","src":"1075:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"915:25:0","parameters":{"id":68,"nodeType":"ParameterList","parameters":[{"constant":false,"id":67,"mutability":"mutable","name":"fnIn","nameLocation":"987:4:0","nodeType":"VariableDeclaration","scope":78,"src":"950:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":66,"nodeType":"FunctionTypeName","parameterTypes":{"id":64,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":66,"src":"959:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":62,"name":"bytes","nodeType":"ElementaryTypeName","src":"959:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"958:14:0"},"returnParameterTypes":{"id":65,"nodeType":"ParameterList","parameters":[],"src":"987:0:0"},"src":"950:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"940:57:0"},"returnParameters":{"id":75,"nodeType":"ParameterList","parameters":[{"constant":false,"id":74,"mutability":"mutable","name":"fnOut","nameLocation":"1058:5:0","nodeType":"VariableDeclaration","scope":78,"src":"1021:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":73,"nodeType":"FunctionTypeName","parameterTypes":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":73,"src":"1030:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":69,"name":"bytes","nodeType":"ElementaryTypeName","src":"1030:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1029:14:0"},"returnParameterTypes":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"1058:0:0"},"src":"1021:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1020:44:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":90,"nodeType":"FunctionDefinition","src":"1133:133:0","nodes":[],"body":{"id":89,"nodeType":"Block","src":"1194:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":86,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":80,"src":"1251:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":84,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1230:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":83,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":78,"src":"1204:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":85,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":87,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1204:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":88,"nodeType":"ExpressionStatement","src":"1204:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1142:15:0","parameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"payload","nameLocation":"1171:7:0","nodeType":"VariableDeclaration","scope":90,"src":"1158:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":79,"name":"bytes","nodeType":"ElementaryTypeName","src":"1158:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1157:22:0"},"returnParameters":{"id":82,"nodeType":"ParameterList","parameters":[],"src":"1194:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1272:380:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1336:316:0","nodes":[],"statements":[{"assignments":[96],"declarations":[{"constant":false,"id":96,"mutability":"mutable","name":"payloadLength","nameLocation":"1354:13:0","nodeType":"VariableDeclaration","scope":105,"src":"1346:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":95,"name":"uint256","nodeType":"ElementaryTypeName","src":"1346:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":99,"initialValue":{"expression":{"id":97,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":92,"src":"1370:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":98,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1370:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1346:38:0"},{"assignments":[101],"declarations":[{"constant":false,"id":101,"mutability":"mutable","name":"consoleAddress","nameLocation":"1402:14:0","nodeType":"VariableDeclaration","scope":105,"src":"1394:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":100,"name":"address","nodeType":"ElementaryTypeName","src":"1394:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":103,"initialValue":{"id":102,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":61,"src":"1419:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1394:40:0"},{"AST":{"nodeType":"YulBlock","src":"1496:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1510:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1534:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1543:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1530:3:0"},"nodeType":"YulFunctionCall","src":"1530:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1514:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1559:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1579:3:0"},"nodeType":"YulFunctionCall","src":"1579:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1586:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1602:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1616:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1631:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"1634:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1568:10:0"},"nodeType":"YulFunctionCall","src":"1568:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1563:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":101,"isOffset":false,"isSlot":false,"src":"1586:14:0","valueSize":1},{"declaration":92,"isOffset":false,"isSlot":false,"src":"1534:7:0","valueSize":1},{"declaration":96,"isOffset":false,"isSlot":false,"src":"1616:13:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1487:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1281:19:0","parameters":{"id":93,"nodeType":"ParameterList","parameters":[{"constant":false,"id":92,"mutability":"mutable","name":"payload","nameLocation":"1314:7:0","nodeType":"VariableDeclaration","scope":106,"src":"1301:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":91,"name":"bytes","nodeType":"ElementaryTypeName","src":"1301:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1300:22:0"},"returnParameters":{"id":94,"nodeType":"ParameterList","parameters":[],"src":"1336:0:0"},"scope":192,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":120,"nodeType":"FunctionDefinition","src":"1658:121:0","nodes":[],"body":{"id":119,"nodeType":"Block","src":"1703:76:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":114,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1753:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":115,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1768:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":112,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1729:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":113,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1729: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":116,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1729:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":111,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1713:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":117,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1713:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":118,"nodeType":"ExpressionStatement","src":"1713:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1667:3:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"p0","nameLocation":"1685:2:0","nodeType":"VariableDeclaration","scope":120,"src":"1671:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":107,"name":"string","nodeType":"ElementaryTypeName","src":"1671:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"1670:18:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":137,"nodeType":"FunctionDefinition","src":"1785:139:0","nodes":[],"body":{"id":136,"nodeType":"Block","src":"1839:85:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":130,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1889:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":131,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":122,"src":"1909:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":132,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":124,"src":"1913:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":128,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"1865:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":129,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"1865: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":133,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1865:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":127,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1849:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":134,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1849:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":135,"nodeType":"ExpressionStatement","src":"1849:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1794:3:0","parameters":{"id":125,"nodeType":"ParameterList","parameters":[{"constant":false,"id":122,"mutability":"mutable","name":"p0","nameLocation":"1812:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1798:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":121,"name":"string","nodeType":"ElementaryTypeName","src":"1798:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":124,"mutability":"mutable","name":"p1","nameLocation":"1821:2:0","nodeType":"VariableDeclaration","scope":137,"src":"1816:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":123,"name":"bool","nodeType":"ElementaryTypeName","src":"1816:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"1797:27:0"},"returnParameters":{"id":126,"nodeType":"ParameterList","parameters":[],"src":"1839:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":154,"nodeType":"FunctionDefinition","src":"1930:145:0","nodes":[],"body":{"id":153,"nodeType":"Block","src":"1987:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":147,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2037:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":148,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":139,"src":"2060:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":149,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":141,"src":"2064:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":145,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2013:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":146,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2013: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":150,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2013:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":144,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"1997:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":151,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1997:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":152,"nodeType":"ExpressionStatement","src":"1997:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"1939:3:0","parameters":{"id":142,"nodeType":"ParameterList","parameters":[{"constant":false,"id":139,"mutability":"mutable","name":"p0","nameLocation":"1957:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1943:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":138,"name":"string","nodeType":"ElementaryTypeName","src":"1943:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":141,"mutability":"mutable","name":"p1","nameLocation":"1969:2:0","nodeType":"VariableDeclaration","scope":154,"src":"1961:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":140,"name":"uint256","nodeType":"ElementaryTypeName","src":"1961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1942:30:0"},"returnParameters":{"id":143,"nodeType":"ParameterList","parameters":[],"src":"1987:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":171,"nodeType":"FunctionDefinition","src":"2081:145:0","nodes":[],"body":{"id":170,"nodeType":"Block","src":"2138:88:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":164,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2188:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":165,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":156,"src":"2211:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":166,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":158,"src":"2215:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":162,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2164:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":163,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2164: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":167,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2164:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":161,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2148:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":168,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2148:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":169,"nodeType":"ExpressionStatement","src":"2148:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2090:3:0","parameters":{"id":159,"nodeType":"ParameterList","parameters":[{"constant":false,"id":156,"mutability":"mutable","name":"p0","nameLocation":"2108:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2094:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":155,"name":"string","nodeType":"ElementaryTypeName","src":"2094:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":158,"mutability":"mutable","name":"p1","nameLocation":"2120:2:0","nodeType":"VariableDeclaration","scope":171,"src":"2112:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":157,"name":"address","nodeType":"ElementaryTypeName","src":"2112:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2093:30:0"},"returnParameters":{"id":160,"nodeType":"ParameterList","parameters":[],"src":"2138:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":191,"nodeType":"FunctionDefinition","src":"2232:179:0","nodes":[],"body":{"id":190,"nodeType":"Block","src":"2313:98:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":183,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2363:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":184,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":173,"src":"2392:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":185,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":175,"src":"2396:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":186,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":177,"src":"2400:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":181,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2339:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":182,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2339: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":187,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2339:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":180,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":90,"src":"2323:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":188,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2323:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":189,"nodeType":"ExpressionStatement","src":"2323:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2241:3:0","parameters":{"id":178,"nodeType":"ParameterList","parameters":[{"constant":false,"id":173,"mutability":"mutable","name":"p0","nameLocation":"2259:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2245:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":172,"name":"string","nodeType":"ElementaryTypeName","src":"2245:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":175,"mutability":"mutable","name":"p1","nameLocation":"2277:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2263:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":174,"name":"string","nodeType":"ElementaryTypeName","src":"2263:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":177,"mutability":"mutable","name":"p2","nameLocation":"2295:2:0","nodeType":"VariableDeclaration","scope":191,"src":"2281:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":176,"name":"string","nodeType":"ElementaryTypeName","src":"2281:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2244:54:0"},"returnParameters":{"id":179,"nodeType":"ParameterList","parameters":[],"src":"2313:0:0"},"scope":192,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[192],"name":"console","nameLocation":"799:7:0","scope":720,"usedErrors":[]},{"id":706,"nodeType":"ContractDefinition","src":"2541:3359:0","nodes":[{"id":207,"nodeType":"VariableDeclaration","src":"2571:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"2597:10:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":194,"name":"address","nodeType":"ElementaryTypeName","src":"2571:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":202,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2644:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":201,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"2634:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":203,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2634:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":200,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2626:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":199,"name":"uint256","nodeType":"ElementaryTypeName","src":"2626:7:0","typeDescriptions":{}}},"id":204,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2626:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":198,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2618:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":197,"name":"uint160","nodeType":"ElementaryTypeName","src":"2618:7:0","typeDescriptions":{}}},"id":205,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2618:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":196,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2610:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":195,"name":"address","nodeType":"ElementaryTypeName","src":"2610:7:0","typeDescriptions":{}}},"id":206,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2610:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":213,"nodeType":"VariableDeclaration","src":"2671:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"2692:2:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"},"typeName":{"id":209,"nodeType":"UserDefinedTypeName","pathNode":{"id":208,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":55,"src":"2671:2:0"},"referencedDeclaration":55,"src":"2671:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"value":{"arguments":[{"id":211,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":207,"src":"2700:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":210,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":55,"src":"2697:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$55_$","typeString":"type(contract Vm)"}},"id":212,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2697:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"visibility":"internal"},{"id":215,"nodeType":"VariableDeclaration","src":"2775:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"2790:7:0","scope":706,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":214,"name":"uint256","nodeType":"ElementaryTypeName","src":"2775:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":378,"nodeType":"FunctionDefinition","src":"2887:949:0","nodes":[],"body":{"id":377,"nodeType":"Block","src":"2909:927:0","nodes":[],"statements":[{"assignments":[220],"declarations":[{"constant":false,"id":220,"mutability":"mutable","name":"x","nameLocation":"2924:1:0","nodeType":"VariableDeclaration","scope":377,"src":"2919:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":219,"name":"bool","nodeType":"ElementaryTypeName","src":"2919:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":226,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":223,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2937:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":224,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"2953:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":221,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"2928:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":222,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"2928:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":225,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2928:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"2919:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2981:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":231,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3004:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":227,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"2969:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":229,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":137,"src":"2969:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":232,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2969:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":233,"nodeType":"ExpressionStatement","src":"2969:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":237,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3029:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":240,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3054:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":239,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3046:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":238,"name":"address","nodeType":"ElementaryTypeName","src":"3046:7:0","typeDescriptions":{}}},"id":241,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3046:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":234,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3017:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":236,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3017:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":242,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3017:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":243,"nodeType":"ExpressionStatement","src":"3017:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":247,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3082:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":252,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3120:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":251,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3112:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":250,"name":"address","nodeType":"ElementaryTypeName","src":"3112:7:0","typeDescriptions":{}}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3112:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":248,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3100:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":249,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3100:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":254,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3100:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":244,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3070:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":246,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3070:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":255,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3070:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":256,"nodeType":"ExpressionStatement","src":"3070:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":260,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3149:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":263,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3172:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3172:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":262,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3164:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":261,"name":"address","nodeType":"ElementaryTypeName","src":"3164:7:0","typeDescriptions":{}}},"id":265,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3164:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":257,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3137:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":259,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3137:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":266,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3137:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":267,"nodeType":"ExpressionStatement","src":"3137:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":271,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3206:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":276,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3242:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3242:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":275,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3234:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":274,"name":"address","nodeType":"ElementaryTypeName","src":"3234:7:0","typeDescriptions":{}}},"id":278,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3234:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":272,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3222:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":273,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3222:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":279,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3222:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":268,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3194:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3194:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":280,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3194:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":281,"nodeType":"ExpressionStatement","src":"3194:61:0"},{"assignments":[283],"declarations":[{"constant":false,"id":283,"mutability":"mutable","name":"json","nameLocation":"3280:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3266:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":282,"name":"string","nodeType":"ElementaryTypeName","src":"3266:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":285,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":284,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3287:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3266:55:0"},{"assignments":[290],"declarations":[{"constant":false,"id":290,"mutability":"mutable","name":"keys","nameLocation":"3347:4:0","nodeType":"VariableDeclaration","scope":377,"src":"3331:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":288,"name":"string","nodeType":"ElementaryTypeName","src":"3331:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":289,"nodeType":"ArrayTypeName","src":"3331:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":296,"initialValue":{"arguments":[{"id":293,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":283,"src":"3371:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":294,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3377:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":291,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3354:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3354:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":295,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3354:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3331:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":300,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3414:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":301,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3422:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":303,"indexExpression":{"hexValue":"30","id":302,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3427:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3422:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":304,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":290,"src":"3431:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":306,"indexExpression":{"hexValue":"31","id":305,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3436:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"3431:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":297,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3402:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":299,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":191,"src":"3402:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3402:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":308,"nodeType":"ExpressionStatement","src":"3402:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3461:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":309,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3450:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":311,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3450:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":313,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3450:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":314,"nodeType":"ExpressionStatement","src":"3450:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"3517:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":321,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3509:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":320,"name":"uint160","nodeType":"ElementaryTypeName","src":"3509:7:0","typeDescriptions":{}}},"id":323,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":319,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3501:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":318,"name":"address","nodeType":"ElementaryTypeName","src":"3501:7:0","typeDescriptions":{}}},"id":324,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3501:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":315,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3487:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":317,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"3487:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":325,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3487:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":326,"nodeType":"ExpressionStatement","src":"3487:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3545:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":327,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3534:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":329,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3534:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":331,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3534:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":332,"nodeType":"ExpressionStatement","src":"3534:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":336,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3582:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":339,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3617:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":340,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3617:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":338,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3609:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":337,"name":"address","nodeType":"ElementaryTypeName","src":"3609:7:0","typeDescriptions":{}}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3609:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":333,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3570:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3570:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":342,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3570:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":343,"nodeType":"ExpressionStatement","src":"3570:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":347,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3651:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":350,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3689:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3681:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":348,"name":"address","nodeType":"ElementaryTypeName","src":"3681:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":344,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3639:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":346,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"3639:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":352,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3639:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":353,"nodeType":"ExpressionStatement","src":"3639:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":357,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3716:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":354,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3705:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":356,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3705:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":358,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3705:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":359,"nodeType":"ExpressionStatement","src":"3705:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":360,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3741:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":362,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"3741:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3741:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":364,"nodeType":"ExpressionStatement","src":"3741:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":368,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3776:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":365,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3765:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":367,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":633,"src":"3765:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3765:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":370,"nodeType":"ExpressionStatement","src":"3765:33:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":374,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3821:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":371,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3809:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":373,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"3809:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":375,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3809:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":376,"nodeType":"ExpressionStatement","src":"3809:20:0"}]},"documentation":{"id":216,"nodeType":"StructuredDocumentation","src":"2804:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"2896:3:0","parameters":{"id":217,"nodeType":"ParameterList","parameters":[],"src":"2899:2:0"},"returnParameters":{"id":218,"nodeType":"ParameterList","parameters":[],"src":"2909:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":609,"nodeType":"FunctionDefinition","src":"3903:1258:0","nodes":[],"body":{"id":608,"nodeType":"Block","src":"3934:1227:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3956:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":392,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3999:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":391,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3991:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":390,"name":"address","nodeType":"ElementaryTypeName","src":"3991:7:0","typeDescriptions":{}}},"id":393,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3991:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"3979:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":389,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3979:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":394,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3979:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":387,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3971:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":386,"name":"uint256","nodeType":"ElementaryTypeName","src":"3971:7:0","typeDescriptions":{}}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3971:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":382,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"3944:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"3944:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":396,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3944:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":397,"nodeType":"ExpressionStatement","src":"3944:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":401,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4030:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":398,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4018:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":400,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4018:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":402,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4018:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":403,"nodeType":"ExpressionStatement","src":"4018:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":404,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4057:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":406,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"4057:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":407,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4057:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":408,"nodeType":"ExpressionStatement","src":"4057:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4092:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":409,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4081:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":411,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4081:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":413,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4081:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":414,"nodeType":"ExpressionStatement","src":"4081:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":418,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4128:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":415,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4117:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":417,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4117:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4117:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":420,"nodeType":"ExpressionStatement","src":"4117:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":424,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4166:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":421,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4154:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":423,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4154:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":425,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4154:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":426,"nodeType":"ExpressionStatement","src":"4154:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":434,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4231:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":433,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4223:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":432,"name":"uint160","nodeType":"ElementaryTypeName","src":"4223:7:0","typeDescriptions":{}}},"id":435,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4223:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":431,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4215:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":430,"name":"address","nodeType":"ElementaryTypeName","src":"4215:7:0","typeDescriptions":{}}},"id":436,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4215:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":427,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4197:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":429,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4197:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":437,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4197:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":438,"nodeType":"ExpressionStatement","src":"4197:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":442,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4263:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":439,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4252:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":441,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4252:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":443,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4252:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":444,"nodeType":"ExpressionStatement","src":"4252:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":448,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4302:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":445,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4291:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":663,"src":"4291:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":449,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4291:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":450,"nodeType":"ExpressionStatement","src":"4291:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":454,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4344:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":451,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4330:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":705,"src":"4330:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":455,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4330:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":456,"nodeType":"ExpressionStatement","src":"4330:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":457,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4371:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":459,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4371:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":460,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4371:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":461,"nodeType":"ExpressionStatement","src":"4371:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":465,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4410:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":462,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4399:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":464,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":648,"src":"4399:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":466,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4399:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":467,"nodeType":"ExpressionStatement","src":"4399:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":471,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4451:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":468,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4439:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":470,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4439:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4439:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":473,"nodeType":"ExpressionStatement","src":"4439:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":481,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4512:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":480,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4504:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":479,"name":"uint160","nodeType":"ElementaryTypeName","src":"4504:7:0","typeDescriptions":{}}},"id":482,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4504:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":478,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4496:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":477,"name":"address","nodeType":"ElementaryTypeName","src":"4496:7:0","typeDescriptions":{}}},"id":483,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4496:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":474,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4478:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":476,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"4478:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4478:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":485,"nodeType":"ExpressionStatement","src":"4478:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":489,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4544:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":486,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4531:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":488,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":678,"src":"4531:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":490,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4531:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":491,"nodeType":"ExpressionStatement","src":"4531:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":492,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4563:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":494,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"4563:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4563:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":496,"nodeType":"ExpressionStatement","src":"4563:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":500,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4604:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":497,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4592:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":499,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4592:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4592:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":502,"nodeType":"ExpressionStatement","src":"4592:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":510,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4665:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4657:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":508,"name":"uint160","nodeType":"ElementaryTypeName","src":"4657:7:0","typeDescriptions":{}}},"id":511,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":507,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4649:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":506,"name":"address","nodeType":"ElementaryTypeName","src":"4649:7:0","typeDescriptions":{}}},"id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4649:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":503,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4636:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":505,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4636:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":513,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4636:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":514,"nodeType":"ExpressionStatement","src":"4636:40:0"},{"assignments":[517],"declarations":[{"constant":false,"id":517,"mutability":"mutable","name":"x","nameLocation":"4693:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4686:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":516,"nodeType":"UserDefinedTypeName","pathNode":{"id":515,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4686:6:0"},"referencedDeclaration":719,"src":"4686:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":523,"initialValue":{"arguments":[{"hexValue":"31323334","id":521,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4708:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":520,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4697:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":519,"nodeType":"UserDefinedTypeName","pathNode":{"id":518,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4701:6:0"},"referencedDeclaration":719,"src":"4701:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":522,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4697:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4686:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":529,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":525,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":517,"src":"4731:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":526,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4731:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4731:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":528,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4742:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4731:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":524,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4723:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":530,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4723:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":531,"nodeType":"ExpressionStatement","src":"4723:24:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":535,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4770:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":532,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4758:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":534,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4758:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":536,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4758:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":537,"nodeType":"ExpressionStatement","src":"4758:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":545,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4820:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":544,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4812:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":543,"name":"uint160","nodeType":"ElementaryTypeName","src":"4812:7:0","typeDescriptions":{}}},"id":546,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4812:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":542,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4804:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":541,"name":"address","nodeType":"ElementaryTypeName","src":"4804:7:0","typeDescriptions":{}}},"id":547,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4804:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":538,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"4791:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":540,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"4791:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4791:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":549,"nodeType":"ExpressionStatement","src":"4791:38:0"},{"assignments":[552],"declarations":[{"constant":false,"id":552,"mutability":"mutable","name":"y","nameLocation":"4846:1:0","nodeType":"VariableDeclaration","scope":608,"src":"4839:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"},"typeName":{"id":551,"nodeType":"UserDefinedTypeName","pathNode":{"id":550,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4839:6:0"},"referencedDeclaration":719,"src":"4839:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"visibility":"internal"}],"id":566,"initialValue":{"arguments":[{"hexValue":"31323334","id":564,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4889:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":555,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"4850:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":554,"nodeType":"UserDefinedTypeName","pathNode":{"id":553,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"4854:6:0"},"referencedDeclaration":719,"src":"4854:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":563,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4883:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":559,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4875:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":558,"name":"uint256","nodeType":"ElementaryTypeName","src":"4875:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4875:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":557,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4867:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":556,"name":"bytes32","nodeType":"ElementaryTypeName","src":"4867:7:0","typeDescriptions":{}}},"id":562,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4867:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"4850:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":565,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4850:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"4839:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":568,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":552,"src":"4912:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":569,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":708,"src":"4912:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":570,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4912:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":571,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4923:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"4912:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":567,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"4904:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4904:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"4904:24:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4950:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"4938:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"4938:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4938:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"4938:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5042:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5042:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":584,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5042:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":585,"nodeType":"ExpressionStatement","src":"5042:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5077:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":588,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5066:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$719_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":587,"nodeType":"UserDefinedTypeName","pathNode":{"id":586,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":719,"src":"5070:6:0"},"referencedDeclaration":719,"src":"5070:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}}},"id":590,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5066:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$719","typeString":"contract FooBar"}},"id":591,"nodeType":"ExpressionStatement","src":"5066:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":595,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5105:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":602,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5146:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}],"id":601,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5138:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":600,"name":"address","nodeType":"ElementaryTypeName","src":"5138:7:0","typeDescriptions":{}}},"id":603,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5138:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":598,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"5126:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$55","typeString":"contract Vm"}},"id":599,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5126:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5126:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":597,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5118:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":596,"name":"uint256","nodeType":"ElementaryTypeName","src":"5118:7:0","typeDescriptions":{}}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5118:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":592,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5093:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":594,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":154,"src":"5093:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":606,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5093:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":607,"nodeType":"ExpressionStatement","src":"5093:61:0"}]},"documentation":{"id":379,"nodeType":"StructuredDocumentation","src":"3842:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"3912:12:0","parameters":{"id":380,"nodeType":"ParameterList","parameters":[],"src":"3924:2:0"},"returnParameters":{"id":381,"nodeType":"ParameterList","parameters":[],"src":"3934:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":633,"nodeType":"FunctionDefinition","src":"5256:143:0","nodes":[],"body":{"id":632,"nodeType":"Block","src":"5305:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":618,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":612,"src":"5327:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":615,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5315:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":617,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5315:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5315:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":620,"nodeType":"ExpressionStatement","src":"5315:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5352:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":627,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"5380:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":628,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"5380:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":626,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5372:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":625,"name":"address","nodeType":"ElementaryTypeName","src":"5372:7:0","typeDescriptions":{}}},"id":629,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5372:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":621,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5340:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":623,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":171,"src":"5340:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":630,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5340:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":631,"nodeType":"ExpressionStatement","src":"5340:52:0"}]},"documentation":{"id":610,"nodeType":"StructuredDocumentation","src":"5167:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"5265:5:0","parameters":{"id":613,"nodeType":"ParameterList","parameters":[{"constant":false,"id":612,"mutability":"mutable","name":"_v","nameLocation":"5287:2:0","nodeType":"VariableDeclaration","scope":633,"src":"5271:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":611,"name":"string","nodeType":"ElementaryTypeName","src":"5271:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5270:20:0"},"returnParameters":{"id":614,"nodeType":"ParameterList","parameters":[],"src":"5305:0:0"},"scope":706,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":648,"nodeType":"FunctionDefinition","src":"5405:95:0","nodes":[],"body":{"id":647,"nodeType":"Block","src":"5449:51:0","nodes":[],"statements":[{"expression":{"id":639,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5459:9:0","subExpression":{"id":638,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5459:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":640,"nodeType":"ExpressionStatement","src":"5459:9:0"},{"expression":{"arguments":[{"id":644,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":635,"src":"5490:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":641,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5478:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":643,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5478:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":645,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5478:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":646,"nodeType":"ExpressionStatement","src":"5478:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"5414:5:0","parameters":{"id":636,"nodeType":"ParameterList","parameters":[{"constant":false,"id":635,"mutability":"mutable","name":"_v","nameLocation":"5436:2:0","nodeType":"VariableDeclaration","scope":648,"src":"5420:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":634,"name":"string","nodeType":"ElementaryTypeName","src":"5420:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5419:20:0"},"returnParameters":{"id":637,"nodeType":"ParameterList","parameters":[],"src":"5449:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":663,"nodeType":"FunctionDefinition","src":"5506:95:0","nodes":[],"body":{"id":662,"nodeType":"Block","src":"5550:51:0","nodes":[],"statements":[{"expression":{"id":654,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5560:9:0","subExpression":{"id":653,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5560:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":655,"nodeType":"ExpressionStatement","src":"5560:9:0"},{"expression":{"arguments":[{"id":659,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":650,"src":"5591:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":656,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5579:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":658,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5579:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":660,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5579:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":661,"nodeType":"ExpressionStatement","src":"5579:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"5515:5:0","parameters":{"id":651,"nodeType":"ParameterList","parameters":[{"constant":false,"id":650,"mutability":"mutable","name":"_v","nameLocation":"5537:2:0","nodeType":"VariableDeclaration","scope":663,"src":"5521:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":649,"name":"string","nodeType":"ElementaryTypeName","src":"5521:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5520:20:0"},"returnParameters":{"id":652,"nodeType":"ParameterList","parameters":[],"src":"5550:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":678,"nodeType":"FunctionDefinition","src":"5607:98:0","nodes":[],"body":{"id":677,"nodeType":"Block","src":"5653:52:0","nodes":[],"statements":[{"expression":{"id":669,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5663:9:0","subExpression":{"id":668,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5663:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":670,"nodeType":"ExpressionStatement","src":"5663:9:0"},{"expression":{"arguments":[{"id":674,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":665,"src":"5695:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":671,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5682:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$706","typeString":"contract ScriptExample"}},"id":673,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":693,"src":"5682:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":675,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5682:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":676,"nodeType":"ExpressionStatement","src":"5682:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"5616:7:0","parameters":{"id":666,"nodeType":"ParameterList","parameters":[{"constant":false,"id":665,"mutability":"mutable","name":"_v","nameLocation":"5640:2:0","nodeType":"VariableDeclaration","scope":678,"src":"5624:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":664,"name":"string","nodeType":"ElementaryTypeName","src":"5624:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5623:20:0"},"returnParameters":{"id":667,"nodeType":"ParameterList","parameters":[],"src":"5653:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":693,"nodeType":"FunctionDefinition","src":"5711:97:0","nodes":[],"body":{"id":692,"nodeType":"Block","src":"5757:51:0","nodes":[],"statements":[{"expression":{"id":684,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"5767:9:0","subExpression":{"id":683,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":215,"src":"5767:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":685,"nodeType":"ExpressionStatement","src":"5767:9:0"},{"expression":{"arguments":[{"id":689,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":680,"src":"5798:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":686,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5786:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":688,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5786:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":690,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5786:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":691,"nodeType":"ExpressionStatement","src":"5786:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"5720:7:0","parameters":{"id":681,"nodeType":"ParameterList","parameters":[{"constant":false,"id":680,"mutability":"mutable","name":"_v","nameLocation":"5744:2:0","nodeType":"VariableDeclaration","scope":693,"src":"5728:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":679,"name":"string","nodeType":"ElementaryTypeName","src":"5728:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5727:20:0"},"returnParameters":{"id":682,"nodeType":"ParameterList","parameters":[],"src":"5757:0:0"},"scope":706,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":705,"nodeType":"FunctionDefinition","src":"5814:84:0","nodes":[],"body":{"id":704,"nodeType":"Block","src":"5866:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":701,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":695,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":698,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":192,"src":"5876:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$192_$","typeString":"type(library console)"}},"id":700,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":120,"src":"5876:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":702,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5876:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":703,"nodeType":"ExpressionStatement","src":"5876:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"5823:8:0","parameters":{"id":696,"nodeType":"ParameterList","parameters":[{"constant":false,"id":695,"mutability":"mutable","name":"_v","nameLocation":"5848:2:0","nodeType":"VariableDeclaration","scope":705,"src":"5832:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":694,"name":"string","nodeType":"ElementaryTypeName","src":"5832:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"5831:20:0"},"returnParameters":{"id":697,"nodeType":"ParameterList","parameters":[],"src":"5866:0:0"},"scope":706,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[719],"contractKind":"contract","documentation":{"id":193,"nodeType":"StructuredDocumentation","src":"2415:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[706],"name":"ScriptExample","nameLocation":"2550:13:0","scope":720,"usedErrors":[]},{"id":719,"nodeType":"ContractDefinition","src":"5902:96:0","nodes":[{"id":708,"nodeType":"VariableDeclaration","src":"5924:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"5939:3:0","scope":719,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":707,"name":"uint256","nodeType":"ElementaryTypeName","src":"5924:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":718,"nodeType":"FunctionDefinition","src":"5949:47:0","nodes":[],"body":{"id":717,"nodeType":"Block","src":"5972:24:0","nodes":[],"statements":[{"expression":{"id":715,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":713,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":708,"src":"5982:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":714,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":710,"src":"5988:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"5982:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":716,"nodeType":"ExpressionStatement","src":"5982:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":711,"nodeType":"ParameterList","parameters":[{"constant":false,"id":710,"mutability":"mutable","name":"v","nameLocation":"5969:1:0","nodeType":"VariableDeclaration","scope":718,"src":"5961:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":709,"name":"uint256","nodeType":"ElementaryTypeName","src":"5961:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"5960:11:0"},"returnParameters":{"id":712,"nodeType":"ParameterList","parameters":[],"src":"5972:0:0"},"scope":719,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[719],"name":"FooBar","nameLocation":"5911:6:0","scope":720,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file +{"abi":[],"bytecode":{"object":"0x602d6037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c634300080f000a","sourceMap":"1144:1851:0:-:0;;;;;;;;;;;;;;;-1:-1:-1;;;1144:1851:0;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c634300080f000a","sourceMap":"1144:1851:0:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"scripts/ScriptExample.s.sol\":\"console\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"scripts/ScriptExample.s.sol\":{\"keccak256\":\"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28\",\"dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"scripts/ScriptExample.s.sol":"console"},"evmVersion":"london","libraries":{}},"sources":{"scripts/ScriptExample.s.sol":{"keccak256":"0x1fd8237b3b3dff6f5f0dcff6572ad225d40275cdf471b8f6bac1df896c0e56da","urls":["bzz-raw://0e60c01e0c609f4401cb66c7d10819321ca7aec52cfb8b688f57f5ae54ee9f28","dweb:/ipfs/QmXyqERiuKiVaUWmP4XVZXdJvhoPsFvbySF2WWJtKjgSy8"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user"},"devdoc":{"version":1,"kind":"dev"},"ast":{"absolutePath":"scripts/ScriptExample.s.sol","id":969,"exportedSymbols":{"FooBar":[799],"ForkTester":[968],"ForkedContract":[852],"NonceGetter":[833],"ScriptExample":[786],"Vm":[83],"console":[220]},"nodeType":"SourceUnit","src":"32:8375:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","0.8",".15"]},{"id":83,"nodeType":"ContractDefinition","src":"120:969:0","nodes":[{"id":10,"nodeType":"FunctionDefinition","src":"139:91:0","nodes":[],"functionSelector":"4777f3cf","implemented":false,"kind":"function","modifiers":[],"name":"envOr","nameLocation":"148:5:0","parameters":{"id":6,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"name","nameLocation":"170:4:0","nodeType":"VariableDeclaration","scope":10,"src":"154:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":2,"name":"string","nodeType":"ElementaryTypeName","src":"154:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"defaultValue","nameLocation":"181:12:0","nodeType":"VariableDeclaration","scope":10,"src":"176:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":4,"name":"bool","nodeType":"ElementaryTypeName","src":"176:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"153:41:0"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[{"constant":false,"id":8,"mutability":"mutable","name":"value","nameLocation":"223:5:0","nodeType":"VariableDeclaration","scope":10,"src":"218:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"218:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"217:12:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":17,"nodeType":"FunctionDefinition","src":"235:72:0","nodes":[],"functionSelector":"2d0335ab","implemented":false,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"244:8:0","parameters":{"id":13,"nodeType":"ParameterList","parameters":[{"constant":false,"id":12,"mutability":"mutable","name":"account","nameLocation":"261:7:0","nodeType":"VariableDeclaration","scope":17,"src":"253:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":11,"name":"address","nodeType":"ElementaryTypeName","src":"253:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"252:17:0"},"returnParameters":{"id":16,"nodeType":"ParameterList","parameters":[{"constant":false,"id":15,"mutability":"mutable","name":"nonce","nameLocation":"300:5:0","nodeType":"VariableDeclaration","scope":17,"src":"293:12:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"},"typeName":{"id":14,"name":"uint64","nodeType":"ElementaryTypeName","src":"293:6:0","typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"visibility":"internal"}],"src":"292:14:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":27,"nodeType":"FunctionDefinition","src":"312:111:0","nodes":[],"functionSelector":"213e4198","implemented":false,"kind":"function","modifiers":[],"name":"parseJsonKeys","nameLocation":"321:13:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":19,"mutability":"mutable","name":"json","nameLocation":"351:4:0","nodeType":"VariableDeclaration","scope":27,"src":"335:20:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":18,"name":"string","nodeType":"ElementaryTypeName","src":"335:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"key","nameLocation":"373:3:0","nodeType":"VariableDeclaration","scope":27,"src":"357:19:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":20,"name":"string","nodeType":"ElementaryTypeName","src":"357:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"334:43:0"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[{"constant":false,"id":25,"mutability":"mutable","name":"keys","nameLocation":"417:4:0","nodeType":"VariableDeclaration","scope":27,"src":"401:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":23,"name":"string","nodeType":"ElementaryTypeName","src":"401:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":24,"nodeType":"ArrayTypeName","src":"401:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"src":"400:22:0"},"scope":83,"stateMutability":"pure","virtual":false,"visibility":"external"},{"id":32,"nodeType":"FunctionDefinition","src":"428:48:0","nodes":[],"functionSelector":"06447d56","implemented":false,"kind":"function","modifiers":[],"name":"startPrank","nameLocation":"437:10:0","parameters":{"id":30,"nodeType":"ParameterList","parameters":[{"constant":false,"id":29,"mutability":"mutable","name":"msgSender","nameLocation":"456:9:0","nodeType":"VariableDeclaration","scope":32,"src":"448:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":28,"name":"address","nodeType":"ElementaryTypeName","src":"448:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"447:19:0"},"returnParameters":{"id":31,"nodeType":"ParameterList","parameters":[],"src":"475:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":35,"nodeType":"FunctionDefinition","src":"481:30:0","nodes":[],"functionSelector":"90c5013b","implemented":false,"kind":"function","modifiers":[],"name":"stopPrank","nameLocation":"490:9:0","parameters":{"id":33,"nodeType":"ParameterList","parameters":[],"src":"499:2:0"},"returnParameters":{"id":34,"nodeType":"ParameterList","parameters":[],"src":"510:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":38,"nodeType":"FunctionDefinition","src":"516:30:0","nodes":[],"functionSelector":"afc98040","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"525:9:0","parameters":{"id":36,"nodeType":"ParameterList","parameters":[],"src":"534:2:0"},"returnParameters":{"id":37,"nodeType":"ParameterList","parameters":[],"src":"545:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":43,"nodeType":"FunctionDefinition","src":"551:47:0","nodes":[],"functionSelector":"e6962cdb","implemented":false,"kind":"function","modifiers":[],"name":"broadcast","nameLocation":"560:9:0","parameters":{"id":41,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40,"mutability":"mutable","name":"msgSender","nameLocation":"578:9:0","nodeType":"VariableDeclaration","scope":43,"src":"570:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":39,"name":"address","nodeType":"ElementaryTypeName","src":"570:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"569:19:0"},"returnParameters":{"id":42,"nodeType":"ParameterList","parameters":[],"src":"597:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":48,"nodeType":"FunctionDefinition","src":"603:52:0","nodes":[],"functionSelector":"7fec2a8d","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"612:14:0","parameters":{"id":46,"nodeType":"ParameterList","parameters":[{"constant":false,"id":45,"mutability":"mutable","name":"msgSender","nameLocation":"635:9:0","nodeType":"VariableDeclaration","scope":48,"src":"627:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":44,"name":"address","nodeType":"ElementaryTypeName","src":"627:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"626:19:0"},"returnParameters":{"id":47,"nodeType":"ParameterList","parameters":[],"src":"654:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":51,"nodeType":"FunctionDefinition","src":"660:35:0","nodes":[],"functionSelector":"7fb5297f","implemented":false,"kind":"function","modifiers":[],"name":"startBroadcast","nameLocation":"669:14:0","parameters":{"id":49,"nodeType":"ParameterList","parameters":[],"src":"683:2:0"},"returnParameters":{"id":50,"nodeType":"ParameterList","parameters":[],"src":"694:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":54,"nodeType":"FunctionDefinition","src":"700:34:0","nodes":[],"functionSelector":"76eadd36","implemented":false,"kind":"function","modifiers":[],"name":"stopBroadcast","nameLocation":"709:13:0","parameters":{"id":52,"nodeType":"ParameterList","parameters":[],"src":"722:2:0"},"returnParameters":{"id":53,"nodeType":"ParameterList","parameters":[],"src":"733:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":61,"nodeType":"FunctionDefinition","src":"739:108:0","nodes":[],"functionSelector":"3ebf73b4","implemented":false,"kind":"function","modifiers":[],"name":"getDeployedCode","nameLocation":"748:15:0","parameters":{"id":57,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56,"mutability":"mutable","name":"artifactPath","nameLocation":"780:12:0","nodeType":"VariableDeclaration","scope":61,"src":"764:28:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":55,"name":"string","nodeType":"ElementaryTypeName","src":"764:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"763:30:0"},"returnParameters":{"id":60,"nodeType":"ParameterList","parameters":[{"constant":false,"id":59,"mutability":"mutable","name":"runtimeBytecode","nameLocation":"830:15:0","nodeType":"VariableDeclaration","scope":61,"src":"817:28:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":58,"name":"bytes","nodeType":"ElementaryTypeName","src":"817:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"816:30:0"},"scope":83,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":68,"nodeType":"FunctionDefinition","src":"852:74:0","nodes":[],"functionSelector":"b4d6c782","implemented":false,"kind":"function","modifiers":[],"name":"etch","nameLocation":"861:4:0","parameters":{"id":66,"nodeType":"ParameterList","parameters":[{"constant":false,"id":63,"mutability":"mutable","name":"target","nameLocation":"874:6:0","nodeType":"VariableDeclaration","scope":68,"src":"866:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":62,"name":"address","nodeType":"ElementaryTypeName","src":"866:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":65,"mutability":"mutable","name":"newRuntimeBytecode","nameLocation":"897:18:0","nodeType":"VariableDeclaration","scope":68,"src":"882:33:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":64,"name":"bytes","nodeType":"ElementaryTypeName","src":"882:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"865:51:0"},"returnParameters":{"id":67,"nodeType":"ParameterList","parameters":[],"src":"925:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":73,"nodeType":"FunctionDefinition","src":"931:51:0","nodes":[],"functionSelector":"ea060291","implemented":false,"kind":"function","modifiers":[],"name":"allowCheatcodes","nameLocation":"940:15:0","parameters":{"id":71,"nodeType":"ParameterList","parameters":[{"constant":false,"id":70,"mutability":"mutable","name":"account","nameLocation":"964:7:0","nodeType":"VariableDeclaration","scope":73,"src":"956:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":69,"name":"address","nodeType":"ElementaryTypeName","src":"956:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"955:17:0"},"returnParameters":{"id":72,"nodeType":"ParameterList","parameters":[],"src":"981:0:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":82,"nodeType":"FunctionDefinition","src":"987:100:0","nodes":[],"functionSelector":"71ee464d","implemented":false,"kind":"function","modifiers":[],"name":"createSelectFork","nameLocation":"996:16:0","parameters":{"id":78,"nodeType":"ParameterList","parameters":[{"constant":false,"id":75,"mutability":"mutable","name":"forkName","nameLocation":"1029:8:0","nodeType":"VariableDeclaration","scope":82,"src":"1013:24:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":74,"name":"string","nodeType":"ElementaryTypeName","src":"1013:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":77,"mutability":"mutable","name":"blockNumber","nameLocation":"1047:11:0","nodeType":"VariableDeclaration","scope":82,"src":"1039:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":76,"name":"uint256","nodeType":"ElementaryTypeName","src":"1039:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1012:47:0"},"returnParameters":{"id":81,"nodeType":"ParameterList","parameters":[{"constant":false,"id":80,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":82,"src":"1078:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":79,"name":"uint256","nodeType":"ElementaryTypeName","src":"1078:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1077:9:0"},"scope":83,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"Vm","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[83],"name":"Vm","nameLocation":"130:2:0","scope":969,"usedErrors":[]},{"id":220,"nodeType":"ContractDefinition","src":"1144:1851:0","nodes":[{"id":89,"nodeType":"VariableDeclaration","src":"1166:86:0","nodes":[],"constant":true,"mutability":"constant","name":"CONSOLE_ADDRESS","nameLocation":"1183:15:0","scope":220,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":84,"name":"address","nodeType":"ElementaryTypeName","src":"1166:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"hexValue":"307830303030303030303030303030303030303036333646366537333646366336353265366336663637","id":87,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1209:42:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"value":"0x000000000000000000636F6e736F6c652e6c6f67"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":86,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":85,"name":"address","nodeType":"ElementaryTypeName","src":"1201:7:0","typeDescriptions":{}}},"id":88,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1201:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":106,"nodeType":"FunctionDefinition","src":"1259:235:0","nodes":[],"body":{"id":105,"nodeType":"Block","src":"1432:62:0","nodes":[],"statements":[{"AST":{"nodeType":"YulBlock","src":"1451:37:0","statements":[{"nodeType":"YulAssignment","src":"1465:13:0","value":{"name":"fnIn","nodeType":"YulIdentifier","src":"1474:4:0"},"variableNames":[{"name":"fnOut","nodeType":"YulIdentifier","src":"1465:5:0"}]}]},"evmVersion":"london","externalReferences":[{"declaration":95,"isOffset":false,"isSlot":false,"src":"1474:4:0","valueSize":1},{"declaration":102,"isOffset":false,"isSlot":false,"src":"1465:5:0","valueSize":1}],"id":104,"nodeType":"InlineAssembly","src":"1442:46:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_castLogPayloadViewToPure","nameLocation":"1268:25:0","parameters":{"id":96,"nodeType":"ParameterList","parameters":[{"constant":false,"id":95,"mutability":"mutable","name":"fnIn","nameLocation":"1331:4:0","nodeType":"VariableDeclaration","scope":106,"src":"1294:41:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"typeName":{"id":94,"nodeType":"FunctionTypeName","parameterTypes":{"id":92,"nodeType":"ParameterList","parameters":[{"constant":false,"id":91,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":94,"src":"1303:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":90,"name":"bytes","nodeType":"ElementaryTypeName","src":"1303:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1302:14:0"},"returnParameterTypes":{"id":93,"nodeType":"ParameterList","parameters":[],"src":"1331:0:0"},"src":"1294:41:0","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) view"},"visibility":"internal"},"visibility":"internal"}],"src":"1293:43:0"},"returnParameters":{"id":103,"nodeType":"ParameterList","parameters":[{"constant":false,"id":102,"mutability":"mutable","name":"fnOut","nameLocation":"1421:5:0","nodeType":"VariableDeclaration","scope":106,"src":"1384:42:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"typeName":{"id":101,"nodeType":"FunctionTypeName","parameterTypes":{"id":99,"nodeType":"ParameterList","parameters":[{"constant":false,"id":98,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":101,"src":"1393:12:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":97,"name":"bytes","nodeType":"ElementaryTypeName","src":"1393:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1392:14:0"},"returnParameterTypes":{"id":100,"nodeType":"ParameterList","parameters":[],"src":"1421:0:0"},"src":"1384:42:0","stateMutability":"pure","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes) pure"},"visibility":"internal"},"visibility":"internal"}],"src":"1383:44:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":118,"nodeType":"FunctionDefinition","src":"1500:133:0","nodes":[],"body":{"id":117,"nodeType":"Block","src":"1561:72:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":114,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":108,"src":"1618:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"arguments":[{"id":112,"name":"_sendLogPayloadView","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":134,"src":"1597:19:0","typeDescriptions":{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_function_internal_view$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) view"}],"id":111,"name":"_castLogPayloadViewToPure","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":106,"src":"1571:25:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_function_internal_view$_t_bytes_memory_ptr_$returns$__$_$returns$_t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$_$","typeString":"function (function (bytes memory) view) pure returns (function (bytes memory) pure)"}},"id":113,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":115,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1571:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":116,"nodeType":"ExpressionStatement","src":"1571:55:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayload","nameLocation":"1509:15:0","parameters":{"id":109,"nodeType":"ParameterList","parameters":[{"constant":false,"id":108,"mutability":"mutable","name":"payload","nameLocation":"1538:7:0","nodeType":"VariableDeclaration","scope":118,"src":"1525:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":107,"name":"bytes","nodeType":"ElementaryTypeName","src":"1525:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1524:22:0"},"returnParameters":{"id":110,"nodeType":"ParameterList","parameters":[],"src":"1561:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":134,"nodeType":"FunctionDefinition","src":"1639:380:0","nodes":[],"body":{"id":133,"nodeType":"Block","src":"1703:316:0","nodes":[],"statements":[{"assignments":[124],"declarations":[{"constant":false,"id":124,"mutability":"mutable","name":"payloadLength","nameLocation":"1721:13:0","nodeType":"VariableDeclaration","scope":133,"src":"1713:21:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":123,"name":"uint256","nodeType":"ElementaryTypeName","src":"1713:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":127,"initialValue":{"expression":{"id":125,"name":"payload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":120,"src":"1737:7:0","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}},"id":126,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"length","nodeType":"MemberAccess","src":"1737:14:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"1713:38:0"},{"assignments":[129],"declarations":[{"constant":false,"id":129,"mutability":"mutable","name":"consoleAddress","nameLocation":"1769:14:0","nodeType":"VariableDeclaration","scope":133,"src":"1761:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":128,"name":"address","nodeType":"ElementaryTypeName","src":"1761:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":131,"initialValue":{"id":130,"name":"CONSOLE_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":89,"src":"1786:15:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"1761:40:0"},{"AST":{"nodeType":"YulBlock","src":"1863:150:0","statements":[{"nodeType":"YulVariableDeclaration","src":"1877:36:0","value":{"arguments":[{"name":"payload","nodeType":"YulIdentifier","src":"1901:7:0"},{"kind":"number","nodeType":"YulLiteral","src":"1910:2:0","type":"","value":"32"}],"functionName":{"name":"add","nodeType":"YulIdentifier","src":"1897:3:0"},"nodeType":"YulFunctionCall","src":"1897:16:0"},"variables":[{"name":"payloadStart","nodeType":"YulTypedName","src":"1881:12:0","type":""}]},{"nodeType":"YulVariableDeclaration","src":"1926:77:0","value":{"arguments":[{"arguments":[],"functionName":{"name":"gas","nodeType":"YulIdentifier","src":"1946:3:0"},"nodeType":"YulFunctionCall","src":"1946:5:0"},{"name":"consoleAddress","nodeType":"YulIdentifier","src":"1953:14:0"},{"name":"payloadStart","nodeType":"YulIdentifier","src":"1969:12:0"},{"name":"payloadLength","nodeType":"YulIdentifier","src":"1983:13:0"},{"kind":"number","nodeType":"YulLiteral","src":"1998:1:0","type":"","value":"0"},{"kind":"number","nodeType":"YulLiteral","src":"2001:1:0","type":"","value":"0"}],"functionName":{"name":"staticcall","nodeType":"YulIdentifier","src":"1935:10:0"},"nodeType":"YulFunctionCall","src":"1935:68:0"},"variables":[{"name":"r","nodeType":"YulTypedName","src":"1930:1:0","type":""}]}]},"documentation":"@solidity memory-safe-assembly","evmVersion":"london","externalReferences":[{"declaration":129,"isOffset":false,"isSlot":false,"src":"1953:14:0","valueSize":1},{"declaration":120,"isOffset":false,"isSlot":false,"src":"1901:7:0","valueSize":1},{"declaration":124,"isOffset":false,"isSlot":false,"src":"1983:13:0","valueSize":1}],"id":132,"nodeType":"InlineAssembly","src":"1854:159:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_sendLogPayloadView","nameLocation":"1648:19:0","parameters":{"id":121,"nodeType":"ParameterList","parameters":[{"constant":false,"id":120,"mutability":"mutable","name":"payload","nameLocation":"1681:7:0","nodeType":"VariableDeclaration","scope":134,"src":"1668:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":119,"name":"bytes","nodeType":"ElementaryTypeName","src":"1668:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1667:22:0"},"returnParameters":{"id":122,"nodeType":"ParameterList","parameters":[],"src":"1703:0:0"},"scope":220,"stateMutability":"view","virtual":false,"visibility":"private"},{"id":148,"nodeType":"FunctionDefinition","src":"2025:164:0","nodes":[],"body":{"id":147,"nodeType":"Block","src":"2070:119:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e6729","id":142,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2120:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},"value":"log(string)"},{"id":143,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":136,"src":"2135:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50","typeString":"literal_string \"log(string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":140,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2096:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":141,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2096: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":144,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2096:42:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":139,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2080:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":145,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2080:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":146,"nodeType":"ExpressionStatement","src":"2080:59:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2034:3:0","parameters":{"id":137,"nodeType":"ParameterList","parameters":[{"constant":false,"id":136,"mutability":"mutable","name":"p0","nameLocation":"2052:2:0","nodeType":"VariableDeclaration","scope":148,"src":"2038:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":135,"name":"string","nodeType":"ElementaryTypeName","src":"2038:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2037:18:0"},"returnParameters":{"id":138,"nodeType":"ParameterList","parameters":[],"src":"2070:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":165,"nodeType":"FunctionDefinition","src":"2195:182:0","nodes":[],"body":{"id":164,"nodeType":"Block","src":"2249:128:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c626f6f6c29","id":158,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2299:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},"value":"log(string,bool)"},{"id":159,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":150,"src":"2319:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":160,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":152,"src":"2323:2:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_c3b556354c088fbb43886eb83c2a04bc7089663f964d22be308197a236f5b870","typeString":"literal_string \"log(string,bool)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":156,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2275:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":157,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2275: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":161,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2275:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":155,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2259:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":162,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2259:68:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":163,"nodeType":"ExpressionStatement","src":"2259:68:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2204:3:0","parameters":{"id":153,"nodeType":"ParameterList","parameters":[{"constant":false,"id":150,"mutability":"mutable","name":"p0","nameLocation":"2222:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2208:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":149,"name":"string","nodeType":"ElementaryTypeName","src":"2208:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":152,"mutability":"mutable","name":"p1","nameLocation":"2231:2:0","nodeType":"VariableDeclaration","scope":165,"src":"2226:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":151,"name":"bool","nodeType":"ElementaryTypeName","src":"2226:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"src":"2207:27:0"},"returnParameters":{"id":154,"nodeType":"ParameterList","parameters":[],"src":"2249:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":182,"nodeType":"FunctionDefinition","src":"2383:188:0","nodes":[],"body":{"id":181,"nodeType":"Block","src":"2440:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c75696e7432353629","id":175,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2490:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},"value":"log(string,uint256)"},{"id":176,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":167,"src":"2513:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":177,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":169,"src":"2517:2:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b60e72ccf6d57ab53eb84d7e94a9545806ed7f93c4d5673f11a64f03471e584e","typeString":"literal_string \"log(string,uint256)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":173,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2466:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":174,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2466: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":178,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2466:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":172,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2450:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":179,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2450:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":180,"nodeType":"ExpressionStatement","src":"2450:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2392:3:0","parameters":{"id":170,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"mutability":"mutable","name":"p0","nameLocation":"2410:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2396:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":166,"name":"string","nodeType":"ElementaryTypeName","src":"2396:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":169,"mutability":"mutable","name":"p1","nameLocation":"2422:2:0","nodeType":"VariableDeclaration","scope":182,"src":"2414:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":168,"name":"uint256","nodeType":"ElementaryTypeName","src":"2414:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"2395:30:0"},"returnParameters":{"id":171,"nodeType":"ParameterList","parameters":[],"src":"2440:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":199,"nodeType":"FunctionDefinition","src":"2577:188:0","nodes":[],"body":{"id":198,"nodeType":"Block","src":"2634:131:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c6164647265737329","id":192,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2684:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},"value":"log(string,address)"},{"id":193,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":184,"src":"2707:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":194,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"2711:2:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_319af333460570a1937bf195dd33445c0d0951c59127da6f1f038b9fdce3fd72","typeString":"literal_string \"log(string,address)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":190,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2660:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":191,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2660: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":195,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2660:54:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":189,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2644:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":196,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2644:71:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":197,"nodeType":"ExpressionStatement","src":"2644:71:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2586:3:0","parameters":{"id":187,"nodeType":"ParameterList","parameters":[{"constant":false,"id":184,"mutability":"mutable","name":"p0","nameLocation":"2604:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2590:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":183,"name":"string","nodeType":"ElementaryTypeName","src":"2590:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":186,"mutability":"mutable","name":"p1","nameLocation":"2616:2:0","nodeType":"VariableDeclaration","scope":199,"src":"2608:10:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":185,"name":"address","nodeType":"ElementaryTypeName","src":"2608:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2589:30:0"},"returnParameters":{"id":188,"nodeType":"ParameterList","parameters":[],"src":"2634:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"},{"id":219,"nodeType":"FunctionDefinition","src":"2771:222:0","nodes":[],"body":{"id":218,"nodeType":"Block","src":"2852:141:0","nodes":[],"statements":[{"expression":{"arguments":[{"arguments":[{"hexValue":"6c6f6728737472696e672c737472696e672c737472696e6729","id":211,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2902:27:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},"value":"log(string,string,string)"},{"id":212,"name":"p0","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":201,"src":"2931:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":213,"name":"p1","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":203,"src":"2935:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"id":214,"name":"p2","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":205,"src":"2939:2:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2ced7cef693312206c21f0e92e3b54e2e16bf33db5eec350c78866822c665e1f","typeString":"literal_string \"log(string,string,string)\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":209,"name":"abi","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-1,"src":"2878:3:0","typeDescriptions":{"typeIdentifier":"t_magic_abi","typeString":"abi"}},"id":210,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"memberName":"encodeWithSignature","nodeType":"MemberAccess","src":"2878: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":215,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2878:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":208,"name":"_sendLogPayload","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":118,"src":"2862:15:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory) pure"}},"id":216,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"2862:81:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":217,"nodeType":"ExpressionStatement","src":"2862:81:0"}]},"implemented":true,"kind":"function","modifiers":[],"name":"log","nameLocation":"2780:3:0","parameters":{"id":206,"nodeType":"ParameterList","parameters":[{"constant":false,"id":201,"mutability":"mutable","name":"p0","nameLocation":"2798:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2784:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":200,"name":"string","nodeType":"ElementaryTypeName","src":"2784:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":203,"mutability":"mutable","name":"p1","nameLocation":"2816:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2802:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":202,"name":"string","nodeType":"ElementaryTypeName","src":"2802:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"},{"constant":false,"id":205,"mutability":"mutable","name":"p2","nameLocation":"2834:2:0","nodeType":"VariableDeclaration","scope":219,"src":"2820:16:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":204,"name":"string","nodeType":"ElementaryTypeName","src":"2820:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"2783:54:0"},"returnParameters":{"id":207,"nodeType":"ParameterList","parameters":[],"src":"2852:0:0"},"scope":220,"stateMutability":"pure","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[],"canonicalName":"console","contractDependencies":[],"contractKind":"library","fullyImplemented":true,"linearizedBaseContracts":[220],"name":"console","nameLocation":"1152:7:0","scope":969,"usedErrors":[]},{"id":786,"nodeType":"ContractDefinition","src":"3123:3912:0","nodes":[{"id":235,"nodeType":"VariableDeclaration","src":"3152:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"3178:10:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":222,"name":"address","nodeType":"ElementaryTypeName","src":"3152:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":230,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3225:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":229,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"3215:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":231,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3215:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":228,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3207:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":227,"name":"uint256","nodeType":"ElementaryTypeName","src":"3207:7:0","typeDescriptions":{}}},"id":232,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3207:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":226,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3199:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":225,"name":"uint160","nodeType":"ElementaryTypeName","src":"3199:7:0","typeDescriptions":{}}},"id":233,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3199:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":224,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3191:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":223,"name":"address","nodeType":"ElementaryTypeName","src":"3191:7:0","typeDescriptions":{}}},"id":234,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3191:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":241,"nodeType":"VariableDeclaration","src":"3252:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"3273:2:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":237,"nodeType":"UserDefinedTypeName","pathNode":{"id":236,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"3252:2:0"},"referencedDeclaration":83,"src":"3252:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":239,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":235,"src":"3281:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":238,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"3278:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":240,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3278:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":243,"nodeType":"VariableDeclaration","src":"3356:22:0","nodes":[],"constant":false,"functionSelector":"61bc221a","mutability":"mutable","name":"counter","nameLocation":"3371:7:0","scope":786,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":242,"name":"uint256","nodeType":"ElementaryTypeName","src":"3356:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":456,"nodeType":"FunctionDefinition","src":"3468:1428:0","nodes":[],"body":{"id":455,"nodeType":"Block","src":"3490:1406:0","nodes":[],"statements":[{"assignments":[248],"declarations":[{"constant":false,"id":248,"mutability":"mutable","name":"x","nameLocation":"3505:1:0","nodeType":"VariableDeclaration","scope":455,"src":"3500:6:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"typeName":{"id":247,"name":"bool","nodeType":"ElementaryTypeName","src":"3500:4:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"visibility":"internal"}],"id":254,"initialValue":{"arguments":[{"hexValue":"4558414d504c455f424f4f4c","id":251,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3518:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},"value":"EXAMPLE_BOOL"},{"hexValue":"66616c7365","id":252,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"3534:5:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a634dae177a0e138ae7aaa2afae347412e148992e88c7aabd33ee71be146cb7f","typeString":"literal_string \"EXAMPLE_BOOL\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":249,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3509:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":250,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"envOr","nodeType":"MemberAccess","referencedDeclaration":10,"src":"3509:8:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$_t_bool_$returns$_t_bool_$","typeString":"function (string memory,bool) view external returns (bool)"}},"id":253,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3509:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"VariableDeclarationStatement","src":"3500:40:0"},{"expression":{"arguments":[{"hexValue":"626f6f6c2076616c75652066726f6d20656e76","id":258,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3562:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},"value":"bool value from env"},{"id":259,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":248,"src":"3585:1:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5a607d0b5a1295325aa8901721d78ba402601bba6f62cebdd5235dd0204a590b","typeString":"literal_string \"bool value from env\""},{"typeIdentifier":"t_bool","typeString":"bool"}],"expression":{"id":255,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3550:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":257,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":165,"src":"3550:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_bool_$returns$__$","typeString":"function (string memory,bool) pure"}},"id":260,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3550:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":261,"nodeType":"ExpressionStatement","src":"3550:37:0"},{"expression":{"arguments":[{"hexValue":"636f6e74726163742061646472","id":265,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3610:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},"value":"contract addr"},{"arguments":[{"id":268,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3635:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":267,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3627:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":266,"name":"address","nodeType":"ElementaryTypeName","src":"3627:7:0","typeDescriptions":{}}},"id":269,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3627:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa50728770d00fe8f6a0592f3565bbfaf063ee4077f1f5bbc003d091d33cd0c4","typeString":"literal_string \"contract addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":262,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3598:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":264,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3598:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":270,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3598:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":271,"nodeType":"ExpressionStatement","src":"3598:43:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206e6f6e6365","id":275,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3663:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},"value":"contract nonce"},{"arguments":[{"arguments":[{"id":280,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"3701:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":279,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3693:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":278,"name":"address","nodeType":"ElementaryTypeName","src":"3693:7:0","typeDescriptions":{}}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3693:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":276,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3681:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":277,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3681:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":282,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3681:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_3a23091615a5de8c0a35ffd8857a37e2c4e0b72f3ef8a34d6caf65efcd562e2f","typeString":"literal_string \"contract nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":272,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3651:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":274,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3651:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":283,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3651:57:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":284,"nodeType":"ExpressionStatement","src":"3651:57:0"},{"expression":{"arguments":[{"hexValue":"73656e6465722061646472","id":288,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3730:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},"value":"sender addr"},{"arguments":[{"expression":{"id":291,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3753:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":292,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3753:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":290,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3745:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":289,"name":"address","nodeType":"ElementaryTypeName","src":"3745:7:0","typeDescriptions":{}}},"id":293,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3745:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8125ca2decf812b25b65606ff16dad37cb198ff0433485a7926e50feafacfc35","typeString":"literal_string \"sender addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":285,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3718:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"3718:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3718:47:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":295,"nodeType":"ExpressionStatement","src":"3718:47:0"},{"expression":{"arguments":[{"hexValue":"73656e646572206e6f6e6365","id":299,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3787:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},"value":"sender nonce"},{"arguments":[{"arguments":[{"expression":{"id":304,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"3823:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":305,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"3823:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":303,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"3815:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":302,"name":"address","nodeType":"ElementaryTypeName","src":"3815:7:0","typeDescriptions":{}}},"id":306,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3815:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":300,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3803:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"3803:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3803:32:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_db7deb43f2f9e0404016de53b7e64c4976b54149581f7534daae2551e8cf4e40","typeString":"literal_string \"sender nonce\""},{"typeIdentifier":"t_uint64","typeString":"uint64"}],"expression":{"id":296,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3775:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":298,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"3775:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":308,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3775:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":309,"nodeType":"ExpressionStatement","src":"3775:61:0"},{"assignments":[311],"declarations":[{"constant":false,"id":311,"mutability":"mutable","name":"json","nameLocation":"3861:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3847:18:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":310,"name":"string","nodeType":"ElementaryTypeName","src":"3847:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":313,"initialValue":{"hexValue":"7b22726f6f745f6b6579223a205b7b2261223a20312c202262223a20327d5d7d","id":312,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3868:34:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_e95522e99766888d0261f55bd1eae5e3f3e26eaf009a16e2433eafaf0a4ecdf2","typeString":"literal_string \"{\"root_key\": [{\"a\": 1, \"b\": 2}]}\""},"value":"{\"root_key\": [{\"a\": 1, \"b\": 2}]}"},"nodeType":"VariableDeclarationStatement","src":"3847:55:0"},{"assignments":[318],"declarations":[{"constant":false,"id":318,"mutability":"mutable","name":"keys","nameLocation":"3928:4:0","nodeType":"VariableDeclaration","scope":455,"src":"3912:20:0","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string[]"},"typeName":{"baseType":{"id":316,"name":"string","nodeType":"ElementaryTypeName","src":"3912:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"id":317,"nodeType":"ArrayTypeName","src":"3912:8:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_storage_$dyn_storage_ptr","typeString":"string[]"}},"visibility":"internal"}],"id":324,"initialValue":{"arguments":[{"id":321,"name":"json","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":311,"src":"3952:4:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"hexValue":"2e726f6f745f6b65795b305d","id":322,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3958:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""},"value":".root_key[0]"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_stringliteral_d82f67100edb80050915e1ec4b565c9a8319a22efb1075e1298b7bb60101d266","typeString":"literal_string \".root_key[0]\""}],"expression":{"id":319,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"3935:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":320,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"parseJsonKeys","nodeType":"MemberAccess","referencedDeclaration":27,"src":"3935:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$_t_array$_t_string_memory_ptr_$dyn_memory_ptr_$","typeString":"function (string memory,string memory) pure external returns (string memory[] memory)"}},"id":323,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3935:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"nodeType":"VariableDeclarationStatement","src":"3912:61:0"},{"expression":{"arguments":[{"hexValue":"6b657973","id":328,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"3995:6:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},"value":"keys"},{"baseExpression":{"id":329,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4003:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":331,"indexExpression":{"hexValue":"30","id":330,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4008:1:0","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4003:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},{"baseExpression":{"id":332,"name":"keys","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":318,"src":"4012:4:0","typeDescriptions":{"typeIdentifier":"t_array$_t_string_memory_ptr_$dyn_memory_ptr","typeString":"string memory[] memory"}},"id":334,"indexExpression":{"hexValue":"31","id":333,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4017:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"4012:7:0","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f29790a80c4ce5f42f59892f424f9c92856c6b656c3378e2cf305b260c6f4195","typeString":"literal_string \"keys\""},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"},{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}],"expression":{"id":325,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"3983:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":327,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":219,"src":"3983:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory,string memory) pure"}},"id":335,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"3983:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":336,"nodeType":"ExpressionStatement","src":"3983:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c","id":340,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4042:15:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""},"value":"from original"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_77928970c8757d110f3c23e003246f49e0de890480ba9717ba659b2f56f316b2","typeString":"literal_string \"from original\""}],"expression":{"id":337,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4031:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":339,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4031:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":341,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4031:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":342,"nodeType":"ExpressionStatement","src":"4031:27:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"30783432","id":350,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"4098:4:0","typeDescriptions":{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"},"value":"0x42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_66_by_1","typeString":"int_const 66"}],"id":349,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4090:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":348,"name":"uint160","nodeType":"ElementaryTypeName","src":"4090:7:0","typeDescriptions":{}}},"id":351,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4090:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":347,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4082:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":346,"name":"address","nodeType":"ElementaryTypeName","src":"4082:7:0","typeDescriptions":{}}},"id":352,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4082:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":343,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4068:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":345,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startPrank","nodeType":"MemberAccess","referencedDeclaration":32,"src":"4068:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":353,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4068:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":354,"nodeType":"ExpressionStatement","src":"4068:37:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2031","id":358,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4126:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""},"value":"from prank 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_42b34abfe37a8b0add910cda7b4a379e6538fa7a1dcafce47a02bd38f6c88e2a","typeString":"literal_string \"from prank 1\""}],"expression":{"id":355,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4115:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":357,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4115:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":359,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4115:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":360,"nodeType":"ExpressionStatement","src":"4115:26:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f7065206d73672e73656e646572","id":364,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4163:25:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},"value":"parent scope msg.sender"},{"arguments":[{"expression":{"id":367,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"4198:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":368,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"4198:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":366,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4190:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":365,"name":"address","nodeType":"ElementaryTypeName","src":"4190:7:0","typeDescriptions":{}}},"id":369,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4190:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_83ec9246154d8845de47aafc5c2865c9985d2efe84472c27283879f2fbf5cc94","typeString":"literal_string \"parent scope msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":361,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4151:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":363,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4151:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":370,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4151:59:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":371,"nodeType":"ExpressionStatement","src":"4151:59:0"},{"expression":{"arguments":[{"hexValue":"706172656e742073636f706520636f6e74726163742e61646472","id":375,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4232:28:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},"value":"parent scope contract.addr"},{"arguments":[{"id":378,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4270:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":377,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4262:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":376,"name":"address","nodeType":"ElementaryTypeName","src":"4262:7:0","typeDescriptions":{}}},"id":379,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4262:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_97df66250e0b2b48f0ec8d0e01eb1b8ca012d95f1572895622aa1ea433e5570f","typeString":"literal_string \"parent scope contract.addr\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":372,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4220:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":374,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"4220:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":380,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4220:56:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":381,"nodeType":"ExpressionStatement","src":"4220:56:0"},{"expression":{"arguments":[{"hexValue":"66726f6d207072616e6b2032","id":385,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4297:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""},"value":"from prank 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_a38a34f8cad750a79aa097a92971f8f405b51ee9d53d25c5b14fc129ba3684bb","typeString":"literal_string \"from prank 2\""}],"expression":{"id":382,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4286:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":384,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4286:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":386,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4286:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":387,"nodeType":"ExpressionStatement","src":"4286:26:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":388,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4322:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":390,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopPrank","nodeType":"MemberAccess","referencedDeclaration":35,"src":"4322:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":391,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4322:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":392,"nodeType":"ExpressionStatement","src":"4322:14:0"},{"expression":{"arguments":[{"hexValue":"66726f6d206f726967696e616c20616761696e","id":396,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4357:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""},"value":"from original again"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_0c805c6579e20a9c4c8e11aeab23330910a9f2da629191dc119d1730e8ed6860","typeString":"literal_string \"from original again\""}],"expression":{"id":393,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4346:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":395,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"hello","nodeType":"MemberAccess","referencedDeclaration":713,"src":"4346:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) view external"}},"id":397,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4346:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":398,"nodeType":"ExpressionStatement","src":"4346:33:0"},{"assignments":[400],"declarations":[{"constant":false,"id":400,"mutability":"mutable","name":"tmpNonceGetter","nameLocation":"4480:14:0","nodeType":"VariableDeclaration","scope":455,"src":"4472:22:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":399,"name":"address","nodeType":"ElementaryTypeName","src":"4472:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":413,"initialValue":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"74656d70206e6f6e6365207465737420676574746572","id":408,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4531:24:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""},"value":"temp nonce test getter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_12520bf22cf2eb7252f13fda2b7eb7ddaed1b3456e20c8008c714c7ba4d9a252","typeString":"literal_string \"temp nonce test getter\""}],"id":407,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"4521:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":409,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4521:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":406,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4513:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":405,"name":"uint256","nodeType":"ElementaryTypeName","src":"4513:7:0","typeDescriptions":{}}},"id":410,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4513:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":404,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4505:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":403,"name":"uint160","nodeType":"ElementaryTypeName","src":"4505:7:0","typeDescriptions":{}}},"id":411,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4505:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":402,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4497:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":401,"name":"address","nodeType":"ElementaryTypeName","src":"4497:7:0","typeDescriptions":{}}},"id":412,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4497:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"4472:87:0"},{"expression":{"arguments":[{"id":417,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4577:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"arguments":[{"hexValue":"5363726970744578616d706c652e732e736f6c3a4e6f6e6365476574746572","id":420,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4612:33:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""},"value":"ScriptExample.s.sol:NonceGetter"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_6ff7ab2e79e6b7d182bbfccfe7f8e2118d655ff1b4bf1a4f4ed2eab0f3f8c825","typeString":"literal_string \"ScriptExample.s.sol:NonceGetter\""}],"expression":{"id":418,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4593:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":419,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getDeployedCode","nodeType":"MemberAccess","referencedDeclaration":61,"src":"4593:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$","typeString":"function (string memory) view external returns (bytes memory)"}},"id":421,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4593:53:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":414,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4569:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":416,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"etch","nodeType":"MemberAccess","referencedDeclaration":68,"src":"4569:7:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory) external"}},"id":422,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4569:78:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":423,"nodeType":"ExpressionStatement","src":"4569:78:0"},{"expression":{"arguments":[{"id":427,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4676:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":424,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"4657:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":426,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"allowCheatcodes","nodeType":"MemberAccess","referencedDeclaration":73,"src":"4657:18:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":428,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4657:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":429,"nodeType":"ExpressionStatement","src":"4657:34:0"},{"assignments":[431],"declarations":[{"constant":false,"id":431,"mutability":"mutable","name":"v","nameLocation":"4709:1:0","nodeType":"VariableDeclaration","scope":455,"src":"4701:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":430,"name":"uint256","nodeType":"ElementaryTypeName","src":"4701:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":441,"initialValue":{"arguments":[{"arguments":[{"id":438,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"4758:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":437,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"4750:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":436,"name":"address","nodeType":"ElementaryTypeName","src":"4750:7:0","typeDescriptions":{}}},"id":439,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4750:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"arguments":[{"id":433,"name":"tmpNonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":400,"src":"4725:14:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":432,"name":"NonceGetter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":833,"src":"4713:11:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_NonceGetter_$833_$","typeString":"type(contract NonceGetter)"}},"id":434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:27:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_NonceGetter_$833","typeString":"contract NonceGetter"}},"id":435,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":832,"src":"4713:36:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint256_$","typeString":"function (address) view external returns (uint256)"}},"id":440,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4713:51:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"VariableDeclarationStatement","src":"4701:63:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e63652066726f6d206e6f6e6365206765747465722c206e6f206578706c6963697420616363657373207265717569726564207769746820766d2e657463683a","id":445,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4786:68:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},"value":"nonce from nonce getter, no explicit access required with vm.etch:"},{"id":446,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":431,"src":"4856:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_afafcfffb72f22a98864f79a750e1a4a41d7dd81365e873e06ff57a1a9f42b11","typeString":"literal_string \"nonce from nonce getter, no explicit access required with vm.etch:\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":442,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4774:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":444,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"4774:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":447,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4774:84:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":448,"nodeType":"ExpressionStatement","src":"4774:84:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":452,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"4881:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":449,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"4869:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":451,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"4869:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":453,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"4869:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":454,"nodeType":"ExpressionStatement","src":"4869:20:0"}]},"documentation":{"id":244,"nodeType":"StructuredDocumentation","src":"3385:78:0","text":"@notice example function, runs through basic cheat-codes and console logs."},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"3477:3:0","parameters":{"id":245,"nodeType":"ParameterList","parameters":[],"src":"3480:2:0"},"returnParameters":{"id":246,"nodeType":"ParameterList","parameters":[],"src":"3490:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":689,"nodeType":"FunctionDefinition","src":"4963:1333:0","nodes":[],"body":{"id":688,"nodeType":"Block","src":"4994:1302:0","nodes":[],"statements":[{"expression":{"arguments":[{"hexValue":"6e6f6e6365207374617274","id":463,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5016:13:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},"value":"nonce start"},{"arguments":[{"arguments":[{"arguments":[{"id":470,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5059:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":469,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5051:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":468,"name":"address","nodeType":"ElementaryTypeName","src":"5051:7:0","typeDescriptions":{}}},"id":471,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5051:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":466,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5039:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":467,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"5039:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":472,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5039:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":465,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5031:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":464,"name":"uint256","nodeType":"ElementaryTypeName","src":"5031:7:0","typeDescriptions":{}}},"id":473,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5031:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_71efc69b9a13b6bc1e9a14d766ff01c79022262c6daa6532fb5dfb14f8511a20","typeString":"literal_string \"nonce start\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":460,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5004:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":462,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"5004:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":474,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5004:63:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":475,"nodeType":"ExpressionStatement","src":"5004:63:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073696e676c65","id":479,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5090:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""},"value":"testing single"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b75103528423218e7569082dad569ed0d2ce7c0ac770c0812b220e2d369fe474","typeString":"literal_string \"testing single\""}],"expression":{"id":476,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5078:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":478,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5078:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":480,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5078:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":481,"nodeType":"ExpressionStatement","src":"5078:29:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":482,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5117:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":484,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"5117:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":485,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5117:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":486,"nodeType":"ExpressionStatement","src":"5117:14:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c31","id":490,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5152:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""},"value":"single_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_5e1cad6d7a968cfacf2731373e1248ffb11f4886bced66a02a6de1a67ac8f777","typeString":"literal_string \"single_call1\""}],"expression":{"id":487,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5141:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":489,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5141:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":491,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5141:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":492,"nodeType":"ExpressionStatement","src":"5141:26:0"},{"expression":{"arguments":[{"hexValue":"73696e676c655f63616c6c32","id":496,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5188:14:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""},"value":"single_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b37ddaf5d00ad9e6371de3fb71b91eef731fae1e86b768666380f7d44e1ada25","typeString":"literal_string \"single_call2\""}],"expression":{"id":493,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5177:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":495,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5177:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":497,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5177:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":498,"nodeType":"ExpressionStatement","src":"5177:26:0"},{"expression":{"arguments":[{"hexValue":"74657374696e672073746172742f73746f70","id":502,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5226:20:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""},"value":"testing start/stop"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_778e886e3a1c3c5096aca76228832105f3f9269f362effd0e8ce3737787cb784","typeString":"literal_string \"testing start/stop\""}],"expression":{"id":499,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5214:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":501,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5214:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":503,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5214:33:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":504,"nodeType":"ExpressionStatement","src":"5214:33:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078633066666565","id":512,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5291:8:0","typeDescriptions":{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"},"value":"0xc0ffee"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_12648430_by_1","typeString":"int_const 12648430"}],"id":511,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5283:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":510,"name":"uint160","nodeType":"ElementaryTypeName","src":"5283:7:0","typeDescriptions":{}}},"id":513,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5283:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":509,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5275:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":508,"name":"address","nodeType":"ElementaryTypeName","src":"5275:7:0","typeDescriptions":{}}},"id":514,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5275:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":505,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5257:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":507,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5257:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":515,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5257:45:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":516,"nodeType":"ExpressionStatement","src":"5257:45:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c31","id":520,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5323:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""},"value":"startstop_call1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_2fc2682edf10ed478ee3b9a190f6b1c88bb492b300935ce44545a1613cf8f041","typeString":"literal_string \"startstop_call1\""}],"expression":{"id":517,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5312:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":519,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5312:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":521,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5312:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":522,"nodeType":"ExpressionStatement","src":"5312:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c32","id":526,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5362:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""},"value":"startstop_call2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_1a6fd77f04b28bf45d6d0e2dd4c65c0bbfeba174f849e43bb67ebca1c019cda4","typeString":"literal_string \"startstop_call2\""}],"expression":{"id":523,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5351:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":525,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call2","nodeType":"MemberAccess","referencedDeclaration":743,"src":"5351:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":527,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5351:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":528,"nodeType":"ExpressionStatement","src":"5351:29:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f70757265","id":532,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5404:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""},"value":"startstop_pure"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6e9eb1efd186b1d92b54da45026aa97a178e6eaffdf9dbf9f666fc751fb0ff9","typeString":"literal_string \"startstop_pure\""}],"expression":{"id":529,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5390:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":531,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"callPure","nodeType":"MemberAccess","referencedDeclaration":785,"src":"5390:13:0","typeDescriptions":{"typeIdentifier":"t_function_external_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure external"}},"id":533,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5390:31:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":534,"nodeType":"ExpressionStatement","src":"5390:31:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":535,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5431:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":537,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5431:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":538,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5431:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":539,"nodeType":"ExpressionStatement","src":"5431:18:0"},{"expression":{"arguments":[{"hexValue":"737461727473746f705f63616c6c33","id":543,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5470:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""},"value":"startstop_call3"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8eb502bfdc4adda22bd960aa2ae13ce4c0ed8cc3b3791ed65e321a38cdd36f72","typeString":"literal_string \"startstop_call3\""}],"expression":{"id":540,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5459:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":542,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"call1","nodeType":"MemberAccess","referencedDeclaration":728,"src":"5459:10:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5459:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":545,"nodeType":"ExpressionStatement","src":"5459:29:0"},{"expression":{"arguments":[{"hexValue":"74657374696e67206e6573746564","id":549,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5511:16:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""},"value":"testing nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_f92f19f7a5b5b9ce341188bf4e15925f184cdb5ac135c4846ced718f259dbde5","typeString":"literal_string \"testing nested\""}],"expression":{"id":546,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5499:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":548,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5499:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":550,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5499:29:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":551,"nodeType":"ExpressionStatement","src":"5499:29:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307831323334","id":559,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5572:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":558,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5564:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":557,"name":"uint160","nodeType":"ElementaryTypeName","src":"5564:7:0","typeDescriptions":{}}},"id":560,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5564:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":556,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5556:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":555,"name":"address","nodeType":"ElementaryTypeName","src":"5556:7:0","typeDescriptions":{}}},"id":561,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5556:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":552,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5538:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":554,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"startBroadcast","nodeType":"MemberAccess","referencedDeclaration":48,"src":"5538:17:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5538:43:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":563,"nodeType":"ExpressionStatement","src":"5538:43:0"},{"expression":{"arguments":[{"hexValue":"6e6573746564","id":567,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5604:8:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""},"value":"nested"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4d5b14044d78fbf0c9dd8b9c49e35f09ee5a6f5b1b3b8117b5d0e15c8dd2cb09","typeString":"literal_string \"nested\""}],"expression":{"id":564,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"5591:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":566,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested1","nodeType":"MemberAccess","referencedDeclaration":758,"src":"5591:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":568,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5591:22:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":569,"nodeType":"ExpressionStatement","src":"5591:22:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":570,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5623:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":572,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"stopBroadcast","nodeType":"MemberAccess","referencedDeclaration":54,"src":"5623:16:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":573,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5623:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":574,"nodeType":"ExpressionStatement","src":"5623:18:0"},{"expression":{"arguments":[{"hexValue":"636f6e7472616374206465706c6f796d656e74","id":578,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5664:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""},"value":"contract deployment"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_aaf9be86adf9b6872d87eed3526f7c55f3c5d61f4e4dd6d55ef2fcbb8ad0bd57","typeString":"literal_string \"contract deployment\""}],"expression":{"id":575,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5652:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":577,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5652:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":579,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5652:34:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":580,"nodeType":"ExpressionStatement","src":"5652:34:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"3078313233343536","id":588,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5725:8:0","typeDescriptions":{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"},"value":"0x123456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1193046_by_1","typeString":"int_const 1193046"}],"id":587,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5717:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":586,"name":"uint160","nodeType":"ElementaryTypeName","src":"5717:7:0","typeDescriptions":{}}},"id":589,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5717:17:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":585,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5709:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":584,"name":"address","nodeType":"ElementaryTypeName","src":"5709:7:0","typeDescriptions":{}}},"id":590,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5709:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":581,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5696:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":583,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5696:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":591,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5696:40:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":592,"nodeType":"ExpressionStatement","src":"5696:40:0"},{"assignments":[595],"declarations":[{"constant":false,"id":595,"mutability":"mutable","name":"x","nameLocation":"5753:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5746:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":594,"nodeType":"UserDefinedTypeName","pathNode":{"id":593,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5746:6:0"},"referencedDeclaration":799,"src":"5746:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":601,"initialValue":{"arguments":[{"hexValue":"31323334","id":599,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5768:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":598,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5757:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":597,"nodeType":"UserDefinedTypeName","pathNode":{"id":596,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5761:6:0"},"referencedDeclaration":799,"src":"5761:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":600,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5757:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5746:27:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":607,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":603,"name":"x","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":595,"src":"5791:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":604,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"5791:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":605,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5791:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":606,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5802:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"5791:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e20637265617465206973206e6f742031323334","id":608,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5808:35:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""},"value":"FooBar: foo in create is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_cf44a206a1b0f98235522779025d2df914f464e764b8c79ccaa1efde72c4831c","typeString":"literal_string \"FooBar: foo in create is not 1234\""}],"id":602,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"5783:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":609,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5783:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":610,"nodeType":"ExpressionStatement","src":"5783:61:0"},{"expression":{"arguments":[{"hexValue":"6372656174652032","id":614,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"5867:10:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""},"value":"create 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_4411d6d4ffcd00382a95255a63761e69de9810e1236042a5c64948a7b6c04daa","typeString":"literal_string \"create 2\""}],"expression":{"id":611,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"5855:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":613,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"5855:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":615,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5855:23:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":616,"nodeType":"ExpressionStatement","src":"5855:23:0"},{"expression":{"arguments":[{"arguments":[{"arguments":[{"hexValue":"307863616665","id":624,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5917:6:0","typeDescriptions":{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"},"value":"0xcafe"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_51966_by_1","typeString":"int_const 51966"}],"id":623,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5909:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":622,"name":"uint160","nodeType":"ElementaryTypeName","src":"5909:7:0","typeDescriptions":{}}},"id":625,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5909:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":621,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5901:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":620,"name":"address","nodeType":"ElementaryTypeName","src":"5901:7:0","typeDescriptions":{}}},"id":626,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5901:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":617,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"5888:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":619,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":43,"src":"5888:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_address_$returns$__$","typeString":"function (address) external"}},"id":627,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5888:38:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":628,"nodeType":"ExpressionStatement","src":"5888:38:0"},{"assignments":[631],"declarations":[{"constant":false,"id":631,"mutability":"mutable","name":"y","nameLocation":"5943:1:0","nodeType":"VariableDeclaration","scope":688,"src":"5936:8:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"},"typeName":{"id":630,"nodeType":"UserDefinedTypeName","pathNode":{"id":629,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5936:6:0"},"referencedDeclaration":799,"src":"5936:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"visibility":"internal"}],"id":645,"initialValue":{"arguments":[{"hexValue":"31323334","id":643,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5986:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":634,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"5947:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":633,"nodeType":"UserDefinedTypeName","pathNode":{"id":632,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"5951:6:0"},"referencedDeclaration":799,"src":"5951:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":642,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"names":["salt"],"nodeType":"FunctionCallOptions","options":[{"arguments":[{"arguments":[{"hexValue":"3432","id":639,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"5980:2:0","typeDescriptions":{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"},"value":"42"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_42_by_1","typeString":"int_const 42"}],"id":638,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5972:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":637,"name":"uint256","nodeType":"ElementaryTypeName","src":"5972:7:0","typeDescriptions":{}}},"id":640,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5972:11:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":636,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"5964:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":635,"name":"bytes32","nodeType":"ElementaryTypeName","src":"5964:7:0","typeDescriptions":{}}},"id":641,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5964:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"src":"5947:38:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$salt","typeString":"function (uint256) returns (contract FooBar)"}},"id":644,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"5947:44:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"nodeType":"VariableDeclarationStatement","src":"5936:55:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":651,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":647,"name":"y","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":631,"src":"6009:1:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":648,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":788,"src":"6009:5:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":649,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6009:7:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31323334","id":650,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6020:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"},"src":"6009:15:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"466f6f4261723a20666f6f20696e2063726561746532206973206e6f742031323334","id":652,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6026:36:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""},"value":"FooBar: foo in create2 is not 1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_a532f8073e029b895a819f6b1992843ca1cc824c13ad4c6484e05780ac0a57b9","typeString":"literal_string \"FooBar: foo in create2 is not 1234\""}],"id":646,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"6001:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6001:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":654,"nodeType":"ExpressionStatement","src":"6001:62:0"},{"expression":{"arguments":[{"hexValue":"646f6e6521","id":658,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6085:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""},"value":"done!"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_080382d5c9e9e7c5e3d1d33f5e7422740375955180fadff167d8130e0c35f3fc","typeString":"literal_string \"done!\""}],"expression":{"id":655,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6073:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":657,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6073:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":659,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6073:20:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":660,"nodeType":"ExpressionStatement","src":"6073:20:0"},{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":661,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6177:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":663,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"broadcast","nodeType":"MemberAccess","referencedDeclaration":38,"src":"6177:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":664,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6177:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":665,"nodeType":"ExpressionStatement","src":"6177:14:0"},{"expression":{"arguments":[{"hexValue":"31323334","id":669,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"6212:4:0","typeDescriptions":{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"},"value":"1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1234_by_1","typeString":"int_const 1234"}],"id":668,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"NewExpression","src":"6201:10:0","typeDescriptions":{"typeIdentifier":"t_function_creation_nonpayable$_t_uint256_$returns$_t_contract$_FooBar_$799_$","typeString":"function (uint256) returns (contract FooBar)"},"typeName":{"id":667,"nodeType":"UserDefinedTypeName","pathNode":{"id":666,"name":"FooBar","nodeType":"IdentifierPath","referencedDeclaration":799,"src":"6205:6:0"},"referencedDeclaration":799,"src":"6205:6:0","typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}}},"id":670,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6201:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_FooBar_$799","typeString":"contract FooBar"}},"id":671,"nodeType":"ExpressionStatement","src":"6201:16:0"},{"expression":{"arguments":[{"hexValue":"6e6f6e636520656e64","id":675,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6240:11:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},"value":"nonce end"},{"arguments":[{"arguments":[{"arguments":[{"id":682,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6281:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}],"id":681,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6273:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":680,"name":"address","nodeType":"ElementaryTypeName","src":"6273:7:0","typeDescriptions":{}}},"id":683,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6273:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":678,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":241,"src":"6261:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"6261:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":684,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6261:26:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint64","typeString":"uint64"}],"id":677,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6253:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":676,"name":"uint256","nodeType":"ElementaryTypeName","src":"6253:7:0","typeDescriptions":{}}},"id":685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6253:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_fa629e6661ad2a2bdb09cf9a3a276ce0d722482ae5c2887650751be0938847e8","typeString":"literal_string \"nonce end\""},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"expression":{"id":672,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6228:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":674,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":182,"src":"6228:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_uint256_$returns$__$","typeString":"function (string memory,uint256) pure"}},"id":686,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6228:61:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":687,"nodeType":"ExpressionStatement","src":"6228:61:0"}]},"documentation":{"id":457,"nodeType":"StructuredDocumentation","src":"4902:56:0","text":"@notice example function, to test vm.broadcast with."},"functionSelector":"bef03abc","implemented":true,"kind":"function","modifiers":[],"name":"runBroadcast","nameLocation":"4972:12:0","parameters":{"id":458,"nodeType":"ParameterList","parameters":[],"src":"4984:2:0"},"returnParameters":{"id":459,"nodeType":"ParameterList","parameters":[],"src":"4994:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":713,"nodeType":"FunctionDefinition","src":"6391:143:0","nodes":[],"body":{"id":712,"nodeType":"Block","src":"6440:94:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":698,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":692,"src":"6462:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":695,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6450:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":697,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6450:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":699,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6450:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":700,"nodeType":"ExpressionStatement","src":"6450:15:0"},{"expression":{"arguments":[{"hexValue":"68656c6c6f206d73672e73656e646572","id":704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"6487:18:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},"value":"hello msg.sender"},{"arguments":[{"expression":{"id":707,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"6515:3:0","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":708,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"6515:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":706,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"6507:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":705,"name":"address","nodeType":"ElementaryTypeName","src":"6507:7:0","typeDescriptions":{}}},"id":709,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6507:19:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b3cc13bc51228b2c4c4334d82a4772908254dc0e1c512893dd16208ef13efb8e","typeString":"literal_string \"hello msg.sender\""},{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":701,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6475:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":199,"src":"6475:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$_t_address_$returns$__$","typeString":"function (string memory,address) pure"}},"id":710,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6475:52:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":711,"nodeType":"ExpressionStatement","src":"6475:52:0"}]},"documentation":{"id":690,"nodeType":"StructuredDocumentation","src":"6302:84:0","text":"@notice example external function, to force a CALL, and test vm.startPrank with."},"functionSelector":"a777d0dc","implemented":true,"kind":"function","modifiers":[],"name":"hello","nameLocation":"6400:5:0","parameters":{"id":693,"nodeType":"ParameterList","parameters":[{"constant":false,"id":692,"mutability":"mutable","name":"_v","nameLocation":"6422:2:0","nodeType":"VariableDeclaration","scope":713,"src":"6406:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":691,"name":"string","nodeType":"ElementaryTypeName","src":"6406:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6405:20:0"},"returnParameters":{"id":694,"nodeType":"ParameterList","parameters":[],"src":"6440:0:0"},"scope":786,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":728,"nodeType":"FunctionDefinition","src":"6540:95:0","nodes":[],"body":{"id":727,"nodeType":"Block","src":"6584:51:0","nodes":[],"statements":[{"expression":{"id":719,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6594:9:0","subExpression":{"id":718,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6594:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":720,"nodeType":"ExpressionStatement","src":"6594:9:0"},{"expression":{"arguments":[{"id":724,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":715,"src":"6625:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":721,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6613:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":723,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6613:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":725,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6613:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":726,"nodeType":"ExpressionStatement","src":"6613:15:0"}]},"functionSelector":"7e79255d","implemented":true,"kind":"function","modifiers":[],"name":"call1","nameLocation":"6549:5:0","parameters":{"id":716,"nodeType":"ParameterList","parameters":[{"constant":false,"id":715,"mutability":"mutable","name":"_v","nameLocation":"6571:2:0","nodeType":"VariableDeclaration","scope":728,"src":"6555:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":714,"name":"string","nodeType":"ElementaryTypeName","src":"6555:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6554:20:0"},"returnParameters":{"id":717,"nodeType":"ParameterList","parameters":[],"src":"6584:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":743,"nodeType":"FunctionDefinition","src":"6641:95:0","nodes":[],"body":{"id":742,"nodeType":"Block","src":"6685:51:0","nodes":[],"statements":[{"expression":{"id":734,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6695:9:0","subExpression":{"id":733,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6695:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":735,"nodeType":"ExpressionStatement","src":"6695:9:0"},{"expression":{"arguments":[{"id":739,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":730,"src":"6726:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":736,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6714:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":738,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6714:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":740,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6714:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":741,"nodeType":"ExpressionStatement","src":"6714:15:0"}]},"functionSelector":"8d3ef7ca","implemented":true,"kind":"function","modifiers":[],"name":"call2","nameLocation":"6650:5:0","parameters":{"id":731,"nodeType":"ParameterList","parameters":[{"constant":false,"id":730,"mutability":"mutable","name":"_v","nameLocation":"6672:2:0","nodeType":"VariableDeclaration","scope":743,"src":"6656:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":729,"name":"string","nodeType":"ElementaryTypeName","src":"6656:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6655:20:0"},"returnParameters":{"id":732,"nodeType":"ParameterList","parameters":[],"src":"6685:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":758,"nodeType":"FunctionDefinition","src":"6742:98:0","nodes":[],"body":{"id":757,"nodeType":"Block","src":"6788:52:0","nodes":[],"statements":[{"expression":{"id":749,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6798:9:0","subExpression":{"id":748,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6798:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":750,"nodeType":"ExpressionStatement","src":"6798:9:0"},{"expression":{"arguments":[{"id":754,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":745,"src":"6830:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":751,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"6817:4:0","typeDescriptions":{"typeIdentifier":"t_contract$_ScriptExample_$786","typeString":"contract ScriptExample"}},"id":753,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"nested2","nodeType":"MemberAccess","referencedDeclaration":773,"src":"6817:12:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) external"}},"id":755,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6817:16:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":756,"nodeType":"ExpressionStatement","src":"6817:16:0"}]},"functionSelector":"a76ccdfa","implemented":true,"kind":"function","modifiers":[],"name":"nested1","nameLocation":"6751:7:0","parameters":{"id":746,"nodeType":"ParameterList","parameters":[{"constant":false,"id":745,"mutability":"mutable","name":"_v","nameLocation":"6775:2:0","nodeType":"VariableDeclaration","scope":758,"src":"6759:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":744,"name":"string","nodeType":"ElementaryTypeName","src":"6759:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6758:20:0"},"returnParameters":{"id":747,"nodeType":"ParameterList","parameters":[],"src":"6788:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":773,"nodeType":"FunctionDefinition","src":"6846:97:0","nodes":[],"body":{"id":772,"nodeType":"Block","src":"6892:51:0","nodes":[],"statements":[{"expression":{"id":764,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"nodeType":"UnaryOperation","operator":"++","prefix":false,"src":"6902:9:0","subExpression":{"id":763,"name":"counter","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":243,"src":"6902:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":765,"nodeType":"ExpressionStatement","src":"6902:9:0"},{"expression":{"arguments":[{"id":769,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":760,"src":"6933:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":766,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"6921:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":768,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"6921:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":770,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"6921:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":771,"nodeType":"ExpressionStatement","src":"6921:15:0"}]},"functionSelector":"dbf1282f","implemented":true,"kind":"function","modifiers":[],"name":"nested2","nameLocation":"6855:7:0","parameters":{"id":761,"nodeType":"ParameterList","parameters":[{"constant":false,"id":760,"mutability":"mutable","name":"_v","nameLocation":"6879:2:0","nodeType":"VariableDeclaration","scope":773,"src":"6863:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":759,"name":"string","nodeType":"ElementaryTypeName","src":"6863:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6862:20:0"},"returnParameters":{"id":762,"nodeType":"ParameterList","parameters":[],"src":"6892:0:0"},"scope":786,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":785,"nodeType":"FunctionDefinition","src":"6949:84:0","nodes":[],"body":{"id":784,"nodeType":"Block","src":"7001:32:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":781,"name":"_v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":775,"src":"7023:2:0","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_string_calldata_ptr","typeString":"string calldata"}],"expression":{"id":778,"name":"console","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":220,"src":"7011:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_console_$220_$","typeString":"type(library console)"}},"id":780,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"log","nodeType":"MemberAccess","referencedDeclaration":148,"src":"7011:11:0","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory) pure"}},"id":782,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7011:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":783,"nodeType":"ExpressionStatement","src":"7011:15:0"}]},"functionSelector":"7f8b915c","implemented":true,"kind":"function","modifiers":[],"name":"callPure","nameLocation":"6958:8:0","parameters":{"id":776,"nodeType":"ParameterList","parameters":[{"constant":false,"id":775,"mutability":"mutable","name":"_v","nameLocation":"6983:2:0","nodeType":"VariableDeclaration","scope":785,"src":"6967:18:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_string_calldata_ptr","typeString":"string"},"typeName":{"id":774,"name":"string","nodeType":"ElementaryTypeName","src":"6967:6:0","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"6966:20:0"},"returnParameters":{"id":777,"nodeType":"ParameterList","parameters":[],"src":"7001:0:0"},"scope":786,"stateMutability":"pure","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ScriptExample","contractDependencies":[799],"contractKind":"contract","documentation":{"id":221,"nodeType":"StructuredDocumentation","src":"2997:126:0","text":"@title ScriptExample\n @notice ScriptExample is an example script. The Go forge script code tests that it can run this."},"fullyImplemented":true,"linearizedBaseContracts":[786],"name":"ScriptExample","nameLocation":"3132:13:0","scope":969,"usedErrors":[]},{"id":799,"nodeType":"ContractDefinition","src":"7037:96:0","nodes":[{"id":788,"nodeType":"VariableDeclaration","src":"7059:18:0","nodes":[],"constant":false,"functionSelector":"c2985578","mutability":"mutable","name":"foo","nameLocation":"7074:3:0","scope":799,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":787,"name":"uint256","nodeType":"ElementaryTypeName","src":"7059:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"id":798,"nodeType":"FunctionDefinition","src":"7084:47:0","nodes":[],"body":{"id":797,"nodeType":"Block","src":"7107:24:0","nodes":[],"statements":[{"expression":{"id":795,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":793,"name":"foo","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":788,"src":"7117:3:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":794,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":790,"src":"7123:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"7117:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":796,"nodeType":"ExpressionStatement","src":"7117:7:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":791,"nodeType":"ParameterList","parameters":[{"constant":false,"id":790,"mutability":"mutable","name":"v","nameLocation":"7104:1:0","nodeType":"VariableDeclaration","scope":798,"src":"7096:9:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":789,"name":"uint256","nodeType":"ElementaryTypeName","src":"7096:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7095:11:0"},"returnParameters":{"id":792,"nodeType":"ParameterList","parameters":[],"src":"7107:0:0"},"scope":799,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"FooBar","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[799],"name":"FooBar","nameLocation":"7046:6:0","scope":969,"usedErrors":[]},{"id":833,"nodeType":"ContractDefinition","src":"7135:281:0","nodes":[{"id":813,"nodeType":"VariableDeclaration","src":"7162:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7188:10:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":800,"name":"address","nodeType":"ElementaryTypeName","src":"7162:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":808,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7235:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":807,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7225:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":809,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7225:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":806,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7217:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":805,"name":"uint256","nodeType":"ElementaryTypeName","src":"7217:7:0","typeDescriptions":{}}},"id":810,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7217:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":804,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7209:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":803,"name":"uint160","nodeType":"ElementaryTypeName","src":"7209:7:0","typeDescriptions":{}}},"id":811,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7209:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":802,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7201:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":801,"name":"address","nodeType":"ElementaryTypeName","src":"7201:7:0","typeDescriptions":{}}},"id":812,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7201:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":819,"nodeType":"VariableDeclaration","src":"7262:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7283:2:0","scope":833,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":815,"nodeType":"UserDefinedTypeName","pathNode":{"id":814,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7262:2:0"},"referencedDeclaration":83,"src":"7262:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":817,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":813,"src":"7291:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":816,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7288:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":818,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7288:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":832,"nodeType":"FunctionDefinition","src":"7309:105:0","nodes":[],"body":{"id":831,"nodeType":"Block","src":"7372:42:0","nodes":[],"statements":[{"expression":{"arguments":[{"id":828,"name":"_addr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":821,"src":"7401:5:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":826,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":819,"src":"7389:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":827,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7389:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":829,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7389:18:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"functionReturnParameters":825,"id":830,"nodeType":"Return","src":"7382:25:0"}]},"functionSelector":"2d0335ab","implemented":true,"kind":"function","modifiers":[],"name":"getNonce","nameLocation":"7318:8:0","parameters":{"id":822,"nodeType":"ParameterList","parameters":[{"constant":false,"id":821,"mutability":"mutable","name":"_addr","nameLocation":"7335:5:0","nodeType":"VariableDeclaration","scope":832,"src":"7327:13:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":820,"name":"address","nodeType":"ElementaryTypeName","src":"7327:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"7326:15:0"},"returnParameters":{"id":825,"nodeType":"ParameterList","parameters":[{"constant":false,"id":824,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":832,"src":"7363:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":823,"name":"uint256","nodeType":"ElementaryTypeName","src":"7363:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7362:9:0"},"scope":833,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"NonceGetter","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[833],"name":"NonceGetter","nameLocation":"7144:11:0","scope":969,"usedErrors":[]},{"id":852,"nodeType":"ContractDefinition","src":"7418:174:0","nodes":[{"id":835,"nodeType":"VariableDeclaration","src":"7448:18:0","nodes":[],"constant":false,"mutability":"mutable","name":"v","nameLocation":"7465:1:0","scope":852,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":834,"name":"uint256","nodeType":"ElementaryTypeName","src":"7448:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"id":843,"nodeType":"FunctionDefinition","src":"7473:36:0","nodes":[],"body":{"id":842,"nodeType":"Block","src":"7487:22:0","nodes":[],"statements":[{"expression":{"id":840,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":838,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7497:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"hexValue":"31","id":839,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7501:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"7497:5:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":841,"nodeType":"ExpressionStatement","src":"7497:5:0"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":836,"nodeType":"ParameterList","parameters":[],"src":"7484:2:0"},"returnParameters":{"id":837,"nodeType":"ParameterList","parameters":[],"src":"7487:0:0"},"scope":852,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":851,"nodeType":"FunctionDefinition","src":"7515:75:0","nodes":[],"body":{"id":850,"nodeType":"Block","src":"7565:25:0","nodes":[],"statements":[{"expression":{"id":848,"name":"v","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":835,"src":"7582:1:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":847,"id":849,"nodeType":"Return","src":"7575:8:0"}]},"functionSelector":"20965255","implemented":true,"kind":"function","modifiers":[],"name":"getValue","nameLocation":"7524:8:0","parameters":{"id":844,"nodeType":"ParameterList","parameters":[],"src":"7532:2:0"},"returnParameters":{"id":847,"nodeType":"ParameterList","parameters":[{"constant":false,"id":846,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":851,"src":"7556:7:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":845,"name":"uint256","nodeType":"ElementaryTypeName","src":"7556:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"7555:9:0"},"scope":852,"stateMutability":"view","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkedContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[852],"name":"ForkedContract","nameLocation":"7427:14:0","scope":969,"usedErrors":[]},{"id":968,"nodeType":"ContractDefinition","src":"7594:813:0","nodes":[{"id":866,"nodeType":"VariableDeclaration","src":"7620:94:0","nodes":[],"constant":true,"mutability":"constant","name":"VM_ADDRESS","nameLocation":"7646:10:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":853,"name":"address","nodeType":"ElementaryTypeName","src":"7620:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"value":{"arguments":[{"arguments":[{"arguments":[{"arguments":[{"hexValue":"6865766d20636865617420636f6465","id":861,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7693:17:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""},"value":"hevm cheat code"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d","typeString":"literal_string \"hevm cheat code\""}],"id":860,"name":"keccak256","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-8,"src":"7683:9:0","typeDescriptions":{"typeIdentifier":"t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$","typeString":"function (bytes memory) pure returns (bytes32)"}},"id":862,"isConstant":false,"isLValue":false,"isPure":true,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7683:28:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"}],"id":859,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7675:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":858,"name":"uint256","nodeType":"ElementaryTypeName","src":"7675:7:0","typeDescriptions":{}}},"id":863,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7675:37:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":857,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7667:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":856,"name":"uint160","nodeType":"ElementaryTypeName","src":"7667:7:0","typeDescriptions":{}}},"id":864,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7667:46:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":855,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7659:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":854,"name":"address","nodeType":"ElementaryTypeName","src":"7659:7:0","typeDescriptions":{}}},"id":865,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7659:55:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"id":872,"nodeType":"VariableDeclaration","src":"7720:40:0","nodes":[],"constant":true,"mutability":"constant","name":"vm","nameLocation":"7741:2:0","scope":968,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"},"typeName":{"id":868,"nodeType":"UserDefinedTypeName","pathNode":{"id":867,"name":"Vm","nodeType":"IdentifierPath","referencedDeclaration":83,"src":"7720:2:0"},"referencedDeclaration":83,"src":"7720:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"value":{"arguments":[{"id":870,"name":"VM_ADDRESS","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":866,"src":"7749:10:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":869,"name":"Vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":83,"src":"7746:2:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_Vm_$83_$","typeString":"type(contract Vm)"}},"id":871,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7746:14:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"visibility":"internal"},{"id":967,"nodeType":"FunctionDefinition","src":"7767:638:0","nodes":[],"body":{"id":966,"nodeType":"Block","src":"7791:614:0","nodes":[],"statements":[{"assignments":[876],"declarations":[{"constant":false,"id":876,"mutability":"mutable","name":"testAddr","nameLocation":"7809:8:0","nodeType":"VariableDeclaration","scope":966,"src":"7801:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":875,"name":"address","nodeType":"ElementaryTypeName","src":"7801:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"id":884,"initialValue":{"arguments":[{"arguments":[{"hexValue":"307831323334","id":881,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7836:6:0","typeDescriptions":{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"},"value":"0x1234"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_4660_by_1","typeString":"int_const 4660"}],"id":880,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7828:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint160_$","typeString":"type(uint160)"},"typeName":{"id":879,"name":"uint160","nodeType":"ElementaryTypeName","src":"7828:7:0","typeDescriptions":{}}},"id":882,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7828:15:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint160","typeString":"uint160"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_uint160","typeString":"uint160"}],"id":878,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"7820:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":877,"name":"address","nodeType":"ElementaryTypeName","src":"7820:7:0","typeDescriptions":{}}},"id":883,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7820:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"VariableDeclarationStatement","src":"7801:43:0"},{"assignments":[887],"declarations":[{"constant":false,"id":887,"mutability":"mutable","name":"fc","nameLocation":"7869:2:0","nodeType":"VariableDeclaration","scope":966,"src":"7854:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"},"typeName":{"id":886,"nodeType":"UserDefinedTypeName","pathNode":{"id":885,"name":"ForkedContract","nodeType":"IdentifierPath","referencedDeclaration":852,"src":"7854:14:0"},"referencedDeclaration":852,"src":"7854:14:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"visibility":"internal"}],"id":891,"initialValue":{"arguments":[{"id":889,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7889:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":888,"name":"ForkedContract","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":852,"src":"7874:14:0","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ForkedContract_$852_$","typeString":"type(contract ForkedContract)"}},"id":890,"isConstant":false,"isLValue":false,"isPure":false,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7874:24:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"nodeType":"VariableDeclarationStatement","src":"7854:44:0"},{"expression":{"arguments":[{"hexValue":"666f726b31","id":895,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7929:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},"value":"fork1"},{"hexValue":"3132333435","id":896,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7938:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_b6acbb7ba3bf910295048af2ccd655ff20a445d705d49fd56157c24aab14c1a1","typeString":"literal_string \"fork1\""},{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"}],"expression":{"id":892,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7909:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":894,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"7909:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":897,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7909:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":898,"nodeType":"ExpressionStatement","src":"7909:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":905,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":902,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"7974:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":900,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"7962:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":901,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"7962:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":903,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7962:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3132333435","id":904,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"7987:5:0","typeDescriptions":{"typeIdentifier":"t_rational_12345_by_1","typeString":"int_const 12345"},"value":"12345"},"src":"7962:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":906,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"7994:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":899,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"7954:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":907,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"7954:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":908,"nodeType":"ExpressionStatement","src":"7954:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":914,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":910,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8036:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":911,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8036:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":912,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8036:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"31","id":913,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8053:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"src":"8036:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652031","id":915,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8056:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""},"value":"value should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_244bd39a8f8426ed26a6cae45b2ada0383deda0bbc513dfe29f31ab8529d5c7a","typeString":"literal_string \"value should be 1\""}],"id":909,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8028:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":916,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8028:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":917,"nodeType":"ExpressionStatement","src":"8028:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":925,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":919,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8094:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":920,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8094:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"31","id":923,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8122:1:0","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"}],"id":922,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8114:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":921,"name":"uint256","nodeType":"ElementaryTypeName","src":"8114:7:0","typeDescriptions":{}}},"id":924,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8114:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8094:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652031","id":926,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8126:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""},"value":"balance should be 1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675b86838b72d956fe80c51e424164ea5e48d46b089cf53543fefe5ee2c684bf","typeString":"literal_string \"balance should be 1\""}],"id":918,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8086:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":927,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8086:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":928,"nodeType":"ExpressionStatement","src":"8086:62:0"},{"expression":{"arguments":[{"hexValue":"666f726b32","id":932,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8179:7:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},"value":"fork2"},{"hexValue":"3233343536","id":933,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8188:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_261b052a4950a8ec6afce52cd61229704be48859b7177f79ca612a21277827f8","typeString":"literal_string \"fork2\""},{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"}],"expression":{"id":929,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8159:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":931,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"createSelectFork","nodeType":"MemberAccess","referencedDeclaration":82,"src":"8159:19:0","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$_t_string_memory_ptr_$_t_uint256_$returns$_t_uint256_$","typeString":"function (string memory,uint256) external returns (uint256)"}},"id":934,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8159:35:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":935,"nodeType":"ExpressionStatement","src":"8159:35:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint64","typeString":"uint64"},"id":942,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[{"id":939,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8224:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"expression":{"id":937,"name":"vm","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":872,"src":"8212:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_Vm_$83","typeString":"contract Vm"}},"id":938,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getNonce","nodeType":"MemberAccess","referencedDeclaration":17,"src":"8212:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$_t_address_$returns$_t_uint64_$","typeString":"function (address) view external returns (uint64)"}},"id":940,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8212:21:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint64","typeString":"uint64"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"3233343536","id":941,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8237:5:0","typeDescriptions":{"typeIdentifier":"t_rational_23456_by_1","typeString":"int_const 23456"},"value":"23456"},"src":"8212:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"6e6f6e63652073686f756c64206265203132333435","id":943,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8244:23:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""},"value":"nonce should be 12345"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_675408ff346f993e251ba3ee09efb90c23d0de302269ea6afde722ac077acbdb","typeString":"literal_string \"nonce should be 12345\""}],"id":936,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8204:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":944,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8204:64:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":945,"nodeType":"ExpressionStatement","src":"8204:64:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":951,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":947,"name":"fc","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":887,"src":"8286:2:0","typeDescriptions":{"typeIdentifier":"t_contract$_ForkedContract_$852","typeString":"contract ForkedContract"}},"id":948,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"getValue","nodeType":"MemberAccess","referencedDeclaration":851,"src":"8286:11:0","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"}},"id":949,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8286:13:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"32","id":950,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8303:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"src":"8286:18:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"76616c75652073686f756c642062652032","id":952,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8306:19:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""},"value":"value should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_989f7bdcf9093cc756fd0c37255cb127d8c8369545d3f3456d0571522c208b6d","typeString":"literal_string \"value should be 2\""}],"id":946,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8278:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":953,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8278:48:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":954,"nodeType":"ExpressionStatement","src":"8278:48:0"},{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":962,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":956,"name":"testAddr","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":876,"src":"8344:8:0","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":957,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"balance","nodeType":"MemberAccess","src":"8344:16:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"arguments":[{"hexValue":"32","id":960,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"8372:1:0","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"}],"id":959,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"8364:7:0","typeDescriptions":{"typeIdentifier":"t_type$_t_uint256_$","typeString":"type(uint256)"},"typeName":{"id":958,"name":"uint256","nodeType":"ElementaryTypeName","src":"8364:7:0","typeDescriptions":{}}},"id":961,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8364:10:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"8344:30:0","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"62616c616e63652073686f756c642062652032","id":963,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"8376:21:0","typeDescriptions":{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""},"value":"balance should be 2"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_4c35c4bb929f7c1c753e1326d2d04380b315ea3b8a63106213ab37dd0832958a","typeString":"literal_string \"balance should be 2\""}],"id":955,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"8336:7:0","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":964,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"8336:62:0","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":965,"nodeType":"ExpressionStatement","src":"8336:62:0"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"7776:3:0","parameters":{"id":873,"nodeType":"ParameterList","parameters":[],"src":"7779:2:0"},"returnParameters":{"id":874,"nodeType":"ParameterList","parameters":[],"src":"7791:0:0"},"scope":968,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ForkTester","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[968],"name":"ForkTester","nameLocation":"7603:10:0","scope":969,"usedErrors":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-chain-ops/script/with.go b/op-chain-ops/script/with.go index 00560d5a6b6e8..0823bf9b7fbd8 100644 --- a/op-chain-ops/script/with.go +++ b/op-chain-ops/script/with.go @@ -3,6 +3,8 @@ package script import ( "fmt" + "github.com/ethereum-optimism/optimism/op-chain-ops/script/addresses" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -26,10 +28,13 @@ func WithScript[B any](h *Host, name string, contract string) (b *B, cleanup fun return nil, nil, fmt.Errorf("could not load script artifact: %w", err) } - deployer := ScriptDeployer + deployer := addresses.ScriptDeployer deployNonce := h.state.GetNonce(deployer) // compute address of script contract to be deployed addr := crypto.CreateAddress(deployer, deployNonce) + h.Label(addr, contract) + h.AllowCheatcodes(addr) // before constructor execution, give our script cheatcode access + h.state.MakeExcluded(addr) // scripts are persistent across forks // init bindings (with ABI check) bindings, err := MakeBindings[B](h.ScriptBackendFn(addr), func(abiDef string) bool { @@ -51,7 +56,6 @@ func WithScript[B any](h *Host, name string, contract string) (b *B, cleanup fun return nil, nil, fmt.Errorf("deployed to unexpected address %s, expected %s", deployedAddr, addr) } h.RememberArtifact(addr, artifact, contract) - h.Label(addr, contract) return bindings, func() { h.Wipe(addr) }, nil diff --git a/op-chain-ops/solc/types.go b/op-chain-ops/solc/types.go index de6edb90d231e..56ea47a9ad578 100644 --- a/op-chain-ops/solc/types.go +++ b/op-chain-ops/solc/types.go @@ -1,7 +1,6 @@ package solc import ( - "encoding/json" "fmt" "github.com/ethereum/go-ethereum/accounts/abi" @@ -129,5 +128,158 @@ type Ast struct { Id uint `json:"id"` License string `json:"license"` NodeType string `json:"nodeType"` - Nodes json.RawMessage `json:"nodes"` + Nodes []AstNode `json:"nodes"` + Src string `json:"src"` +} + +type AstNode struct { + Id int `json:"id"` + NodeType string `json:"nodeType"` + Src string `json:"src"` + Nodes []AstNode `json:"nodes,omitempty"` + Abstract bool `json:"abstract,omitempty"` + BaseContracts []AstBaseContract `json:"baseContracts,omitempty"` + CanonicalName string `json:"canonicalName,omitempty"` + ContractDependencies []int `json:"contractDependencies,omitempty"` + ContractKind string `json:"contractKind,omitempty"` + Documentation interface{} `json:"documentation,omitempty"` + FullyImplemented bool `json:"fullyImplemented,omitempty"` + LinearizedBaseContracts []int `json:"linearizedBaseContracts,omitempty"` + Name string `json:"name,omitempty"` + NameLocation string `json:"nameLocation,omitempty"` + Scope int `json:"scope,omitempty"` + UsedErrors []int `json:"usedErrors,omitempty"` + UsedEvents []int `json:"usedEvents,omitempty"` + + // Function specific + Body *AstBlock `json:"body,omitempty"` + Parameters *AstParameterList `json:"parameters,omitempty"` + ReturnParameters *AstParameterList `json:"returnParameters,omitempty"` + StateMutability string `json:"stateMutability,omitempty"` + Virtual bool `json:"virtual,omitempty"` + Visibility string `json:"visibility,omitempty"` + + // Variable specific + Constant bool `json:"constant,omitempty"` + Mutability string `json:"mutability,omitempty"` + StateVariable bool `json:"stateVariable,omitempty"` + StorageLocation string `json:"storageLocation,omitempty"` + TypeDescriptions *AstTypeDescriptions `json:"typeDescriptions,omitempty"` + TypeName *AstTypeName `json:"typeName,omitempty"` + + // Expression specific + Expression *Expression `json:"expression,omitempty"` + IsConstant bool `json:"isConstant,omitempty"` + IsLValue bool `json:"isLValue,omitempty"` + IsPure bool `json:"isPure,omitempty"` + LValueRequested bool `json:"lValueRequested,omitempty"` + + // Literal specific + HexValue string `json:"hexValue,omitempty"` + Kind string `json:"kind,omitempty"` + Value interface{} `json:"value,omitempty"` + + // Other fields + Arguments []Expression `json:"arguments,omitempty"` + Condition *Expression `json:"condition,omitempty"` + TrueBody *AstBlock `json:"trueBody,omitempty"` + FalseBody *AstBlock `json:"falseBody,omitempty"` + Operator string `json:"operator,omitempty"` +} + +type AstBaseContract struct { + BaseName *AstTypeName `json:"baseName"` + Id int `json:"id"` + NodeType string `json:"nodeType"` + Src string `json:"src"` +} + +type AstDocumentation struct { + Id int `json:"id"` + NodeType string `json:"nodeType"` + Src string `json:"src"` + Text string `json:"text"` +} + +type AstBlock struct { + Id int `json:"id"` + NodeType string `json:"nodeType"` + Src string `json:"src"` + Statements []AstNode `json:"statements"` +} + +type AstParameterList struct { + Id int `json:"id"` + NodeType string `json:"nodeType"` + Parameters []AstNode `json:"parameters"` + Src string `json:"src"` +} + +type AstTypeDescriptions struct { + TypeIdentifier string `json:"typeIdentifier"` + TypeString string `json:"typeString"` +} + +type AstTypeName struct { + Id int `json:"id"` + Name string `json:"name"` + NodeType string `json:"nodeType"` + Src string `json:"src"` + StateMutability string `json:"stateMutability,omitempty"` + TypeDescriptions *AstTypeDescriptions `json:"typeDescriptions,omitempty"` +} + +type Expression struct { + Id int `json:"id"` + NodeType string `json:"nodeType"` + Src string `json:"src"` + TypeDescriptions *AstTypeDescriptions `json:"typeDescriptions,omitempty"` + Name string `json:"name,omitempty"` + OverloadedDeclarations []int `json:"overloadedDeclarations,omitempty"` + ReferencedDeclaration int `json:"referencedDeclaration,omitempty"` + ArgumentTypes []AstTypeDescriptions `json:"argumentTypes,omitempty"` +} + +type ForgeArtifact struct { + Abi abi.ABI `json:"abi"` + Bytecode CompilerOutputBytecode `json:"bytecode"` + DeployedBytecode CompilerOutputBytecode `json:"deployedBytecode"` + MethodIdentifiers map[string]string `json:"methodIdentifiers"` + RawMetadata string `json:"rawMetadata"` + Metadata ForgeCompilerMetadata `json:"metadata"` + StorageLayout *StorageLayout `json:"storageLayout,omitempty"` + Ast Ast `json:"ast"` + Id int `json:"id"` +} + +type ForgeCompilerMetadata struct { + Compiler ForgeCompilerInfo `json:"compiler"` + Language string `json:"language"` + Output ForgeMetadataOutput `json:"output"` + Settings CompilerSettings `json:"settings"` + Sources map[string]ForgeSourceInfo `json:"sources"` + Version int `json:"version"` +} + +type ForgeCompilerInfo struct { + Version string `json:"version"` +} + +type ForgeMetadataOutput struct { + Abi abi.ABI `json:"abi"` + DevDoc ForgeDocObject `json:"devdoc"` + UserDoc ForgeDocObject `json:"userdoc"` +} + +type ForgeSourceInfo struct { + Keccak256 string `json:"keccak256"` + License string `json:"license"` + Urls []string `json:"urls"` +} + +type ForgeDocObject struct { + Kind string `json:"kind"` + Methods map[string]interface{} `json:"methods"` + Notice string `json:"notice,omitempty"` + Version int `json:"version"` } diff --git a/op-chain-ops/srcmap/solutil.go b/op-chain-ops/srcmap/solutil.go index 6d1aa4c64a3e7..929632cbfd5f6 100644 --- a/op-chain-ops/srcmap/solutil.go +++ b/op-chain-ops/srcmap/solutil.go @@ -238,6 +238,16 @@ func (s *SourceMapTracer) info(codeAddr common.Address, pc uint64) string { func (s *SourceMapTracer) OnOpCode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { op := vm.OpCode(opcode) + var top []string + stk := scope.StackData() + for i := len(stk) - 1; i >= 0; i-- { + top = append(top, stk[i].Hex()) + if len(top) == 4 { + break + } + } + stkInfo := fmt.Sprintf("[%s]", strings.Join(top, ", ")) + if op.IsPush() { var val []byte sc, ok := scope.(*vm.ScopeContext) @@ -248,10 +258,10 @@ func (s *SourceMapTracer) OnOpCode(pc uint64, opcode byte, gas, cost uint64, sco } else { val = []byte("N/A") } - fmt.Fprintf(s.out, "%-40s : pc %x opcode %s (%x)\n", s.info(scope.Address(), pc), pc, op.String(), val) + fmt.Fprintf(s.out, "%-40s : pc %x opcode %s (%x) \t| stk[:%d] %s\n", s.info(scope.Address(), pc), pc, op.String(), val, len(top), stkInfo) return } - fmt.Fprintf(s.out, "%-40s : pc %x opcode %s\n", s.info(scope.Address(), pc), pc, op.String()) + fmt.Fprintf(s.out, "%-40s : pc %x opcode %s \t\t| stk[:%d] %s\n", s.info(scope.Address(), pc), pc, op.String(), len(top), stkInfo) } func (s *SourceMapTracer) OnFault(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { diff --git a/op-challenger/Makefile b/op-challenger/Makefile index 1a7422a4c8796..35a202b99c4c5 100644 --- a/op-challenger/Makefile +++ b/op-challenger/Makefile @@ -1,35 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -VERSION ?= v0.0.0 +DEPRECATED_TARGETS := op-challenger clean test fuzz visualize -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-challenger/version.Version=$(VERSION) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-challenger/version.Meta=$(VERSION_META) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -# Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169 -ifeq ($(shell uname),Darwin) - FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic -endif - -op-challenger: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-challenger ./cmd - -fuzz: - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzKeccak ./game/keccak/matrix - -clean: - rm bin/op-challenger - -test: - go test -v ./... - -visualize: - ./scripts/visualize.sh - -.PHONY: \ - op-challenger \ - clean \ - test \ - visualize +include ../just/deprecated.mk diff --git a/op-challenger/README.md b/op-challenger/README.md index 69420e419cfa3..5efbbd85b3d3e 100644 --- a/op-challenger/README.md +++ b/op-challenger/README.md @@ -1,7 +1,7 @@ # op-challenger The `op-challenger` is a modular **op-stack** challenge agent written in -golang for dispute games including, but not limited to,attestation games, +golang for dispute games including, but not limited to, attestation games, fault games, and validity games. To learn more about dispute games, visit the [fault proof specs][proof-specs]. @@ -23,6 +23,7 @@ accessed by running `./op-challenger --help`. To run `op-challenger` against the local devnet, first clean and run the devnet from the root of the repository. + ```shell make devnet-clean make devnet-up @@ -31,6 +32,7 @@ make devnet-up Then build the `op-challenger` with `make op-challenger`. Run the `op-challenger` with: + ```shell DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json) ./op-challenger/bin/op-challenger \ @@ -85,6 +87,9 @@ in the L2 output oracle. Optionally, you may specify the game type (aka "trace type") using the `--trace-type` flag, which is set to the cannon trace type by default. +For known networks, the `--game-factory-address` option can be replaced by `--network`. See the `--help` output for a +list of predefined networks. + ### move The `move` subcommand can be run with either the `--attack` or `--defend` flag, @@ -154,7 +159,7 @@ If the game is resolved successfully, the result is printed. ```shell ./bin/op-challenger list-games \ --l1-eth-rpc \ - --network + --game-factory-address ``` Prints the games created by the game factory along with their current status. @@ -162,6 +167,9 @@ Prints the games created by the game factory along with their current status. * `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`). * `GAME_FACTORY_ADDRESS` - the address of the dispute game factory contract on L1. +For known networks, the `--game-factory-address` option can be replaced by `--network`. See the `--help` output for a +list of predefined networks. + ### list-claims ```shell @@ -174,3 +182,39 @@ Prints the list of current claims in a dispute game. * `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`). * `GAME_ADDRESS` - the address of the dispute game to list the move in. + +### run-trace + +```shell +./bin/op-challenger run-trace \ + --network= \ + --l1-eth-rpc= \ + --l1-beacon= \ + --l2-eth-rpc= \ + --rollup-rpc= \ + --data-dir= \ + --prestates-url= \ + --run= +``` + +* `NETWORK_NAME` - the name of a predefined L2 network. +* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`). +* `L1_BEACON` - the REST endpoint of the L1 beacon node to use (e.g. `http://localhost:5100`). +* `L2_ETH_RPC` - the RPC endpoint of the L2 execution client to use +* `ROLLUP_RPC` - the RPC endpoint of the L2 consensus client to use +* `DATA_DIR` - the directory to use to store data +* `PRESTATES_URL` - the base URL to download required prestates from +* `RUN_CONFIG` - the trace providers and prestates to run. e.g. `cannon,asterisc-kona/kona-0.1.0-alpha.5/0x03c50fbef46a05f93ea7665fa89015c2108e10c1b4501799c0663774bd35a9c5` + +Testing utility that continuously runs the specified trace providers against real chain data. The trace providers can be +configured with multiple different prestates. This allows testing both the current and potential future prestates with +the fault proofs virtual machine used by the trace provider. + +The same CLI options as `op-challenger` itself are supported to configure the trace providers. The additional `--run` +option allows specifying which prestates to use. The format is `traceType/name/prestateHash` where traceType is the +trace type to use with the prestate (e.g cannon or asterisc-kona), name is an arbitrary name for the prestate to use +when reporting metrics and prestateHash is the hex encoded absolute prestate commitment to use. If name is omitted the +trace type name is used.If the prestateHash is omitted, the absolute prestate hash used for new games on-chain. + +For example to run both the production cannon prestate and a custom +prestate, use `--run cannon,cannon/next-prestate/0x03c1f0d45248190f80430a4c31e24f8108f05f80ff8b16ecb82d20df6b1b43f3`. diff --git a/op-challenger/cmd/main_test.go b/op-challenger/cmd/main_test.go index 529711bbf6605..018b8b423c33d 100644 --- a/op-challenger/cmd/main_test.go +++ b/op-challenger/cmd/main_test.go @@ -90,9 +90,9 @@ func TestL1Beacon(t *testing.T) { func TestTraceType(t *testing.T) { t.Run("Default", func(t *testing.T) { - expectedDefault := types.TraceTypeCannon - cfg := configForArgs(t, addRequiredArgsExcept(expectedDefault, "--trace-type")) - require.Equal(t, []types.TraceType{expectedDefault}, cfg.TraceTypes) + expectedDefault := []types.TraceType{types.TraceTypeCannon, types.TraceTypeAsteriscKona} + cfg := configForArgs(t, addRequiredArgsForMultipleTracesExcept(expectedDefault, "--trace-type")) + require.Equal(t, expectedDefault, cfg.TraceTypes) }) for _, traceType := range types.TraceTypes { @@ -177,6 +177,13 @@ func TestNetwork(t *testing.T) { t.Run("UnknownNetwork", func(t *testing.T) { verifyArgsInvalid(t, "unknown chain: not-a-network", addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--network=not-a-network")) }) + + t.Run("ChainIDAllowedWhenGameFactoryAddressSupplied", func(t *testing.T) { + addr := common.Address{0xbb, 0xcc, 0xdd} + cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--network=1234", "--game-factory-address="+addr.Hex())) + require.Equal(t, addr, cfg.GameFactoryAddress) + require.Equal(t, "1234", cfg.Cannon.Network) + }) } func TestGameAllowlist(t *testing.T) { @@ -287,7 +294,7 @@ func TestAsteriscOpProgramRequiredArgs(t *testing.T) { }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-prestate")) + verifyArgsInvalid(t, "flag prestates-url or asterisc-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-prestate")) }) t.Run("Valid", func(t *testing.T) { @@ -296,13 +303,42 @@ func TestAsteriscOpProgramRequiredArgs(t *testing.T) { }) }) + t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", traceType), func(t *testing.T) { + allPrestateOptions := []string{"--prestates-url", "--asterisc-prestates-url", "--asterisc-prestate"} + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExceptArr(types.TraceTypeAlphabet, allPrestateOptions)) + }) + + t.Run("NotRequiredIfAsteriscPrestatesBaseURLSet", func(t *testing.T) { + configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-prestates-url=http://localhost/foo")) + }) + + t.Run("AsteriscPrestatesBaseURLTakesPrecedence", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) + require.Equal(t, "http://localhost/foo", cfg.AsteriscAbsolutePreStateBaseURL.String()) + }) + + t.Run("RequiredIfAsteriscPrestatesBaseURLNotSet", func(t *testing.T) { + verifyArgsInvalid(t, "flag prestates-url or asterisc-prestate is required", addRequiredArgsExceptArr(traceType, allPrestateOptions)) + }) + + t.Run("Invalid", func(t *testing.T) { + verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=:foo/bar")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=http://localhost/foo")) + require.Equal(t, "http://localhost/foo", cfg.AsteriscAbsolutePreStateBaseURL.String()) + }) + }) + t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestateBaseURL-%v", traceType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-prestates-url")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-prestate")) + verifyArgsInvalid(t, "flag prestates-url or asterisc-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-prestate")) }) t.Run("Valid", func(t *testing.T) { @@ -335,7 +371,7 @@ func TestAsteriscKonaRequiredArgs(t *testing.T) { }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-kona-prestate")) + verifyArgsInvalid(t, "flag prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-kona-prestate")) }) t.Run("Valid", func(t *testing.T) { @@ -350,7 +386,7 @@ func TestAsteriscKonaRequiredArgs(t *testing.T) { }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-kona-prestate")) + verifyArgsInvalid(t, "flag prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-kona-prestate")) }) t.Run("Valid", func(t *testing.T) { @@ -358,6 +394,35 @@ func TestAsteriscKonaRequiredArgs(t *testing.T) { require.Equal(t, "http://localhost/bar", cfg.AsteriscKonaAbsolutePreStateBaseURL.String()) }) }) + + t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", traceType), func(t *testing.T) { + allPrestateOptions := []string{"--prestates-url", "--asterisc-kona-prestates-url", "--asterisc-kona-prestate"} + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExceptArr(types.TraceTypeAlphabet, allPrestateOptions)) + }) + + t.Run("NotRequiredIfAsteriscKonaPrestatesBaseURLSet", func(t *testing.T) { + configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-kona-prestates-url=http://localhost/foo")) + }) + + t.Run("AsteriscKonaPrestatesBaseURLTakesPrecedence", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-kona-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) + require.Equal(t, "http://localhost/foo", cfg.AsteriscKonaAbsolutePreStateBaseURL.String()) + }) + + t.Run("RequiredIfAsteriscKonaPrestatesBaseURLNotSet", func(t *testing.T) { + verifyArgsInvalid(t, "flag prestates-url or asterisc-kona-prestate is required", addRequiredArgsExceptArr(traceType, allPrestateOptions)) + }) + + t.Run("Invalid", func(t *testing.T) { + verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=:foo/bar")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=http://localhost/foo")) + require.Equal(t, "http://localhost/foo", cfg.AsteriscKonaAbsolutePreStateBaseURL.String()) + }) + }) } func TestAsteriscBaseRequiredArgs(t *testing.T) { @@ -579,7 +644,7 @@ func TestCannonRequiredArgs(t *testing.T) { }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate")) + verifyArgsInvalid(t, "flag prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate")) }) t.Run("Valid", func(t *testing.T) { @@ -588,13 +653,13 @@ func TestCannonRequiredArgs(t *testing.T) { }) }) - t.Run(fmt.Sprintf("TestCannonAbsolutePrestateBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonPrestatesBaseURL-%v", traceType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-prestates-url")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate")) + verifyArgsInvalid(t, "flag prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate")) }) t.Run("Valid", func(t *testing.T) { @@ -603,6 +668,35 @@ func TestCannonRequiredArgs(t *testing.T) { }) }) + t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", traceType), func(t *testing.T) { + allPrestateOptions := []string{"--prestates-url", "--cannon-prestates-url", "--cannon-prestate"} + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExceptArr(types.TraceTypeAlphabet, allPrestateOptions)) + }) + + t.Run("NotRequiredIfCannonPrestatesBaseURLSet", func(t *testing.T) { + configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--cannon-prestates-url=http://localhost/foo")) + }) + + t.Run("CannonPrestatesBaseURLTakesPrecedence", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--cannon-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) + require.Equal(t, "http://localhost/foo", cfg.CannonAbsolutePreStateBaseURL.String()) + }) + + t.Run("RequiredIfCannonPrestatesBaseURLNotSet", func(t *testing.T) { + verifyArgsInvalid(t, "flag prestates-url or cannon-prestate is required", addRequiredArgsExceptArr(traceType, allPrestateOptions)) + }) + + t.Run("Invalid", func(t *testing.T) { + verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=:foo/bar")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=http://localhost/foo")) + require.Equal(t, "http://localhost/foo", cfg.CannonAbsolutePreStateBaseURL.String()) + }) + }) + t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) { t.Run("RequiredForCannonTrace", func(t *testing.T) { verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(traceType, "--l2-eth-rpc")) @@ -671,7 +765,7 @@ func TestCannonRequiredArgs(t *testing.T) { t.Run(fmt.Sprintf("TestMustNotSpecifyCannonNetworkAndRollup-%v", traceType), func(t *testing.T) { verifyArgsInvalid( t, - "flag cannon-network can not be used with cannon-rollup-config and cannon-l2-genesis", + "flag cannon-network can not be used with cannon-rollup-config, cannon-l2-genesis or cannon-l2-custom", addRequiredArgsExcept(traceType, "--cannon-network", "--cannon-network", cannonNetwork, "--cannon-rollup-config=rollup.json")) }) @@ -683,9 +777,10 @@ func TestCannonRequiredArgs(t *testing.T) { args["--network"] = cannonNetwork args["--cannon-rollup-config"] = "rollup.json" args["--cannon-l2-genesis"] = "gensis.json" + args["--cannon-l2-custom"] = "true" verifyArgsInvalid( t, - "flag network can not be used with cannon-rollup-config and cannon-l2-genesis", + "flag network can not be used with cannon-rollup-config, cannon-l2-genesis or cannon-l2-custom", toArgList(args)) }) @@ -719,6 +814,14 @@ func TestCannonRequiredArgs(t *testing.T) { }) }) + t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", traceType), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-network", + "--cannon-rollup-config=rollup.json", + "--cannon-l2-genesis=genesis.json", + "--cannon-l2-custom")) + require.True(t, cfg.Cannon.L2Custom) + }) + t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", traceType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-rollup-config")) @@ -850,6 +953,18 @@ func TestAdditionalBondClaimants(t *testing.T) { }) } +func TestSignerTLS(t *testing.T) { + t.Run("EnabledByDefault", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet)) + require.True(t, cfg.TxMgrConfig.SignerCLIConfig.TLSConfig.Enabled) + }) + + t.Run("Disabled", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--signer.tls.enabled=false")) + require.False(t, cfg.TxMgrConfig.SignerCLIConfig.TLSConfig.Enabled) + }) +} + func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) { _, _, err := dryRunWithArgs(cliArgs) require.ErrorContains(t, err, messageContains) @@ -889,6 +1004,30 @@ func addRequiredArgsExcept(traceType types.TraceType, name string, optionalArgs return append(toArgList(req), optionalArgs...) } +func addRequiredArgsForMultipleTracesExcept(traceType []types.TraceType, name string, optionalArgs ...string) []string { + req := requiredArgsMultiple(traceType) + delete(req, name) + return append(toArgList(req), optionalArgs...) +} + +func addRequiredArgsExceptArr(traceType types.TraceType, names []string, optionalArgs ...string) []string { + req := requiredArgs(traceType) + for _, name := range names { + delete(req, name) + } + return append(toArgList(req), optionalArgs...) +} + +func requiredArgsMultiple(traceType []types.TraceType) map[string]string { + args := make(map[string]string) + for _, t := range traceType { + for name, value := range requiredArgs(t) { + args[name] = value + } + } + return args +} + func requiredArgs(traceType types.TraceType) map[string]string { args := map[string]string{ "--l1-eth-rpc": l1EthRpc, @@ -915,7 +1054,6 @@ func addRequiredCannonArgs(args map[string]string) { args["--cannon-bin"] = cannonBin args["--cannon-server"] = cannonServer args["--cannon-prestate"] = cannonPreState - args["--l2-eth-rpc"] = l2EthRpc } func addRequiredAsteriscArgs(args map[string]string) { @@ -923,7 +1061,6 @@ func addRequiredAsteriscArgs(args map[string]string) { args["--asterisc-bin"] = asteriscBin args["--asterisc-server"] = asteriscServer args["--asterisc-prestate"] = asteriscPreState - args["--l2-eth-rpc"] = l2EthRpc } func addRequiredAsteriscKonaArgs(args map[string]string) { @@ -931,7 +1068,6 @@ func addRequiredAsteriscKonaArgs(args map[string]string) { args["--asterisc-bin"] = asteriscBin args["--asterisc-kona-server"] = asteriscServer args["--asterisc-kona-prestate"] = asteriscPreState - args["--l2-eth-rpc"] = l2EthRpc } func toArgList(req map[string]string) []string { diff --git a/op-challenger/cmd/run_trace.go b/op-challenger/cmd/run_trace.go index c1d2261230f9e..7c52d0de13b10 100644 --- a/op-challenger/cmd/run_trace.go +++ b/op-challenger/cmd/run_trace.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" - "net/url" + "slices" + "strings" - "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/flags" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/runner" opservice "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/cliapp" @@ -16,8 +16,12 @@ import ( "github.com/urfave/cli/v2" ) -func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) { +var ( + ErrUnknownTraceType = errors.New("unknown trace type") + ErrInvalidPrestateHash = errors.New("invalid prestate hash") +) +func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) { logger, err := setupLogging(ctx) if err != nil { return nil, err @@ -31,36 +35,21 @@ func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, er if err := cfg.Check(); err != nil { return nil, err } - if err := checkMTCannonFlags(ctx, cfg); err != nil { + runConfigs, err := parseRunArgs(ctx.StringSlice(RunTraceRunFlag.Name)) + if err != nil { return nil, err } - - var mtPrestate common.Hash - var mtPrestateURL *url.URL - if ctx.IsSet(addMTCannonPrestateFlag.Name) { - mtPrestate = common.HexToHash(ctx.String(addMTCannonPrestateFlag.Name)) - mtPrestateURL, err = url.Parse(ctx.String(addMTCannonPrestateURLFlag.Name)) - if err != nil { - return nil, fmt.Errorf("invalid mt-cannon prestate url (%v): %w", ctx.String(addMTCannonPrestateFlag.Name), err) + if len(runConfigs) == 0 { + // Default to running on-chain version of each enabled trace type + for _, traceType := range cfg.TraceTypes { + runConfigs = append(runConfigs, runner.RunConfig{TraceType: traceType}) } } - return runner.NewRunner(logger, cfg, mtPrestate, mtPrestateURL), nil -} - -func checkMTCannonFlags(ctx *cli.Context, cfg *config.Config) error { - if ctx.IsSet(addMTCannonPrestateFlag.Name) || ctx.IsSet(addMTCannonPrestateURLFlag.Name) { - if ctx.IsSet(addMTCannonPrestateFlag.Name) != ctx.IsSet(addMTCannonPrestateURLFlag.Name) { - return fmt.Errorf("both flag %v and %v must be set when running MT-Cannon traces", addMTCannonPrestateURLFlag.Name, addMTCannonPrestateFlag.Name) - } - if cfg.Cannon == (vm.Config{}) { - return errors.New("required Cannon vm configuration for mt-cannon traces is missing") - } - } - return nil + return runner.NewRunner(logger, cfg, runConfigs), nil } func runTraceFlags() []cli.Flag { - return append(flags.Flags, addMTCannonPrestateFlag, addMTCannonPrestateURLFlag) + return append(flags.Flags, RunTraceRunFlag) } var RunTraceCommand = &cli.Command{ @@ -72,14 +61,50 @@ var RunTraceCommand = &cli.Command{ } var ( - addMTCannonPrestateFlag = &cli.StringFlag{ - Name: "add-mt-cannon-prestate", - Usage: "Use this prestate to run MT-Cannon compatibility tests", - EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "ADD_MT_CANNON_PRESTATE"), - } - addMTCannonPrestateURLFlag = &cli.StringFlag{ - Name: "add-mt-cannon-prestate-url", - Usage: "Use this prestate URL to run MT-Cannon compatibility tests", - EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "ADD_MT_CANNON_PRESTATE_URL"), + RunTraceRunFlag = &cli.StringSliceFlag{ + Name: "run", + Usage: "Specify a trace to run. Format is traceType/name/prestateHash where " + + "traceType is the trace type to use with the prestate (e.g cannon or asterisc-kona), " + + "name is an arbitrary name for the prestate to use when reporting metrics and" + + "prestateHash is the hex encoded absolute prestate commitment to use. " + + "If name is omitted the trace type name is used." + + "If the prestateHash is omitted, the absolute prestate hash used for new games on-chain.", + EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "RUN"), } ) + +func parseRunArgs(args []string) ([]runner.RunConfig, error) { + cfgs := make([]runner.RunConfig, len(args)) + for i, arg := range args { + cfg, err := parseRunArg(arg) + if err != nil { + return nil, err + } + cfgs[i] = cfg + } + return cfgs, nil +} + +func parseRunArg(arg string) (runner.RunConfig, error) { + cfg := runner.RunConfig{} + opts := strings.SplitN(arg, "/", 3) + if len(opts) == 0 { + return runner.RunConfig{}, fmt.Errorf("invalid run config %q", arg) + } + cfg.TraceType = types.TraceType(opts[0]) + if !slices.Contains(types.TraceTypes, cfg.TraceType) { + return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", ErrUnknownTraceType, opts[0], arg) + } + if len(opts) > 1 { + cfg.Name = opts[1] + } else { + cfg.Name = cfg.TraceType.String() + } + if len(opts) > 2 { + cfg.Prestate = common.HexToHash(opts[2]) + if cfg.Prestate == (common.Hash{}) { + return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", ErrInvalidPrestateHash, opts[2], arg) + } + } + return cfg, nil +} diff --git a/op-challenger/cmd/run_trace_test.go b/op-challenger/cmd/run_trace_test.go new file mode 100644 index 0000000000000..78e047827dd2b --- /dev/null +++ b/op-challenger/cmd/run_trace_test.go @@ -0,0 +1,35 @@ +package main + +import ( + "strings" + "testing" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/runner" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestParseRunArg(t *testing.T) { + tests := []struct { + arg string + expected runner.RunConfig + err error + }{ + {arg: "unknown/test1/0x1234", err: ErrUnknownTraceType}, + {arg: "cannon", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: types.TraceTypeCannon.String()}}, + {arg: "asterisc", expected: runner.RunConfig{TraceType: types.TraceTypeAsterisc, Name: types.TraceTypeAsterisc.String()}}, + {arg: "cannon/test1", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1"}}, + {arg: "cannon/test1/0x1234", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1", Prestate: common.HexToHash("0x1234")}}, + {arg: "cannon/test1/invalid", err: ErrInvalidPrestateHash}, + } + for _, test := range tests { + test := test + // Slash characters in test names confuse some things that parse the output as it looks like a subtest + t.Run(strings.ReplaceAll(test.arg, "/", "_"), func(t *testing.T) { + actual, err := parseRunArg(test.arg) + require.ErrorIs(t, err, test.err) + require.Equal(t, test.expected, actual) + }) + } +} diff --git a/op-challenger/config/config.go b/op-challenger/config/config.go index e8a245936f33f..6faf7c9fdca3f 100644 --- a/op-challenger/config/config.go +++ b/op-challenger/config/config.go @@ -6,6 +6,7 @@ import ( "net/url" "runtime" "slices" + "strconv" "time" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" @@ -18,37 +19,46 @@ import ( ) var ( - ErrMissingTraceType = errors.New("no supported trace types specified") - ErrMissingDatadir = errors.New("missing datadir") - ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0") - ErrMissingL2Rpc = errors.New("missing L2 rpc url") - ErrMissingCannonBin = errors.New("missing cannon bin") - ErrMissingCannonServer = errors.New("missing cannon server") - ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state") - ErrCannonAbsolutePreStateAndBaseURL = errors.New("only specify one of cannon absolute pre-state and cannon absolute pre-state base URL") - ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url") - ErrMissingL1Beacon = errors.New("missing l1 beacon url") - ErrMissingGameFactoryAddress = errors.New("missing game factory address") - ErrMissingCannonSnapshotFreq = errors.New("missing cannon snapshot freq") - ErrMissingCannonInfoFreq = errors.New("missing cannon info freq") - ErrMissingCannonRollupConfig = errors.New("missing cannon network or rollup config path") - ErrMissingCannonL2Genesis = errors.New("missing cannon network or l2 genesis path") - ErrCannonNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path") - ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") - ErrCannonNetworkUnknown = errors.New("unknown cannon network") - ErrMissingRollupRpc = errors.New("missing rollup rpc url") + ErrMissingTraceType = errors.New("no supported trace types specified") + ErrMissingDatadir = errors.New("missing datadir") + ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0") + ErrMissingL2Rpc = errors.New("missing L2 rpc url") + ErrMissingCannonBin = errors.New("missing cannon bin") + ErrMissingCannonServer = errors.New("missing cannon server") + ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state") + ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url") + ErrMissingL1Beacon = errors.New("missing l1 beacon url") + ErrMissingGameFactoryAddress = errors.New("missing game factory address") + ErrMissingCannonSnapshotFreq = errors.New("missing cannon snapshot freq") + ErrMissingCannonInfoFreq = errors.New("missing cannon info freq") + ErrMissingCannonRollupConfig = errors.New("missing cannon network or rollup config path") + ErrMissingCannonL2Genesis = errors.New("missing cannon network or l2 genesis path") + ErrCannonNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path") + ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") + ErrCannonNetworkUnknown = errors.New("unknown cannon network") + ErrMissingRollupRpc = errors.New("missing rollup rpc url") - ErrMissingAsteriscBin = errors.New("missing asterisc bin") - ErrMissingAsteriscServer = errors.New("missing asterisc server") - ErrMissingAsteriscAbsolutePreState = errors.New("missing asterisc absolute pre-state") - ErrAsteriscAbsolutePreStateAndBaseURL = errors.New("only specify one of asterisc absolute pre-state and asterisc absolute pre-state base URL") - ErrMissingAsteriscSnapshotFreq = errors.New("missing asterisc snapshot freq") - ErrMissingAsteriscInfoFreq = errors.New("missing asterisc info freq") - ErrMissingAsteriscRollupConfig = errors.New("missing asterisc network or rollup config path") - ErrMissingAsteriscL2Genesis = errors.New("missing asterisc network or l2 genesis path") - ErrAsteriscNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path") - ErrAsteriscNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") - ErrAsteriscNetworkUnknown = errors.New("unknown asterisc network") + ErrMissingAsteriscBin = errors.New("missing asterisc bin") + ErrMissingAsteriscServer = errors.New("missing asterisc server") + ErrMissingAsteriscAbsolutePreState = errors.New("missing asterisc absolute pre-state") + ErrMissingAsteriscSnapshotFreq = errors.New("missing asterisc snapshot freq") + ErrMissingAsteriscInfoFreq = errors.New("missing asterisc info freq") + ErrMissingAsteriscRollupConfig = errors.New("missing asterisc network or rollup config path") + ErrMissingAsteriscL2Genesis = errors.New("missing asterisc network or l2 genesis path") + ErrAsteriscNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path") + ErrAsteriscNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") + ErrAsteriscNetworkUnknown = errors.New("unknown asterisc network") + + ErrMissingAsteriscKonaBin = errors.New("missing asterisc kona bin") + ErrMissingAsteriscKonaServer = errors.New("missing asterisc kona server") + ErrMissingAsteriscKonaAbsolutePreState = errors.New("missing asterisc kona absolute pre-state") + ErrMissingAsteriscKonaSnapshotFreq = errors.New("missing asterisc kona snapshot freq") + ErrMissingAsteriscKonaInfoFreq = errors.New("missing asterisc kona info freq") + ErrMissingAsteriscKonaRollupConfig = errors.New("missing asterisc kona network or rollup config path") + ErrMissingAsteriscKonaL2Genesis = errors.New("missing asterisc kona network or l2 genesis path") + ErrAsteriscKonaNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path") + ErrAsteriscKonaNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path") + ErrAsteriscKonaNetworkUnknown = errors.New("unknown asterisc kona network") ) const ( @@ -149,20 +159,22 @@ func NewConfig( BinarySnapshots: true, }, Asterisc: vm.Config{ - VmType: types.TraceTypeAsterisc, - L1: l1EthRpc, - L1Beacon: l1BeaconApi, - L2: l2EthRpc, - SnapshotFreq: DefaultAsteriscSnapshotFreq, - InfoFreq: DefaultAsteriscInfoFreq, + VmType: types.TraceTypeAsterisc, + L1: l1EthRpc, + L1Beacon: l1BeaconApi, + L2: l2EthRpc, + SnapshotFreq: DefaultAsteriscSnapshotFreq, + InfoFreq: DefaultAsteriscInfoFreq, + BinarySnapshots: true, }, AsteriscKona: vm.Config{ - VmType: types.TraceTypeAsteriscKona, - L1: l1EthRpc, - L1Beacon: l1BeaconApi, - L2: l2EthRpc, - SnapshotFreq: DefaultAsteriscSnapshotFreq, - InfoFreq: DefaultAsteriscInfoFreq, + VmType: types.TraceTypeAsteriscKona, + L1: l1EthRpc, + L1Beacon: l1BeaconApi, + L2: l2EthRpc, + SnapshotFreq: DefaultAsteriscSnapshotFreq, + InfoFreq: DefaultAsteriscInfoFreq, + BinarySnapshots: true, }, GameWindow: DefaultGameWindow, } @@ -219,15 +231,15 @@ func (c Config) Check() error { return ErrCannonNetworkAndL2Genesis } if ch := chaincfg.ChainByName(c.Cannon.Network); ch == nil { - return fmt.Errorf("%w: %v", ErrCannonNetworkUnknown, c.Cannon.Network) + // Check if this looks like a chain ID that could be a custom chain configuration. + if _, err := strconv.ParseUint(c.Cannon.Network, 10, 32); err != nil { + return fmt.Errorf("%w: %v", ErrCannonNetworkUnknown, c.Cannon.Network) + } } } if c.CannonAbsolutePreState == "" && c.CannonAbsolutePreStateBaseURL == nil { return ErrMissingCannonAbsolutePreState } - if c.CannonAbsolutePreState != "" && c.CannonAbsolutePreStateBaseURL != nil { - return ErrCannonAbsolutePreStateAndBaseURL - } if c.Cannon.SnapshotFreq == 0 { return ErrMissingCannonSnapshotFreq } @@ -263,9 +275,6 @@ func (c Config) Check() error { if c.AsteriscAbsolutePreState == "" && c.AsteriscAbsolutePreStateBaseURL == nil { return ErrMissingAsteriscAbsolutePreState } - if c.AsteriscAbsolutePreState != "" && c.AsteriscAbsolutePreStateBaseURL != nil { - return ErrAsteriscAbsolutePreStateAndBaseURL - } if c.Asterisc.SnapshotFreq == 0 { return ErrMissingAsteriscSnapshotFreq } @@ -273,6 +282,41 @@ func (c Config) Check() error { return ErrMissingAsteriscInfoFreq } } + if c.TraceTypeEnabled(types.TraceTypeAsteriscKona) { + if c.AsteriscKona.VmBin == "" { + return ErrMissingAsteriscKonaBin + } + if c.AsteriscKona.Server == "" { + return ErrMissingAsteriscKonaServer + } + if c.AsteriscKona.Network == "" { + if c.AsteriscKona.RollupConfigPath == "" { + return ErrMissingAsteriscKonaRollupConfig + } + if c.AsteriscKona.L2GenesisPath == "" { + return ErrMissingAsteriscKonaL2Genesis + } + } else { + if c.AsteriscKona.RollupConfigPath != "" { + return ErrAsteriscKonaNetworkAndRollupConfig + } + if c.AsteriscKona.L2GenesisPath != "" { + return ErrAsteriscKonaNetworkAndL2Genesis + } + if ch := chaincfg.ChainByName(c.AsteriscKona.Network); ch == nil { + return fmt.Errorf("%w: %v", ErrAsteriscKonaNetworkUnknown, c.AsteriscKona.Network) + } + } + if c.AsteriscKonaAbsolutePreState == "" && c.AsteriscKonaAbsolutePreStateBaseURL == nil { + return ErrMissingAsteriscKonaAbsolutePreState + } + if c.AsteriscKona.SnapshotFreq == 0 { + return ErrMissingAsteriscKonaSnapshotFreq + } + if c.AsteriscKona.InfoFreq == 0 { + return ErrMissingAsteriscKonaInfoFreq + } + } if err := c.TxMgrConfig.Check(); err != nil { return err } diff --git a/op-challenger/config/config_test.go b/op-challenger/config/config_test.go index 4181fd069bede..bf910ab83e855 100644 --- a/op-challenger/config/config_test.go +++ b/op-challenger/config/config_test.go @@ -31,10 +31,17 @@ var ( validAsteriscNetwork = "mainnet" validAsteriscAbsolutePreState = "pre.json" validAsteriscAbsolutePreStateBaseURL, _ = url.Parse("http://localhost/bar/") + + validAsteriscKonaBin = "./bin/asterisc" + validAsteriscKonaServerBin = "./bin/kona-host" + validAsteriscKonaNetwork = "mainnet" + validAsteriscKonaAbsolutePreState = "pre.json" + validAsteriscKonaAbsolutePreStateBaseURL, _ = url.Parse("http://localhost/bar/") ) var cannonTraceTypes = []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned} var asteriscTraceTypes = []types.TraceType{types.TraceTypeAsterisc} +var asteriscKonaTraceTypes = []types.TraceType{types.TraceTypeAsteriscKona} func applyValidConfigForCannon(cfg *Config) { cfg.Cannon.VmBin = validCannonBin @@ -50,6 +57,13 @@ func applyValidConfigForAsterisc(cfg *Config) { cfg.Asterisc.Network = validAsteriscNetwork } +func applyValidConfigForAsteriscKona(cfg *Config) { + cfg.AsteriscKona.VmBin = validAsteriscKonaBin + cfg.AsteriscKona.Server = validAsteriscKonaServerBin + cfg.AsteriscKonaAbsolutePreStateBaseURL = validAsteriscKonaAbsolutePreStateBaseURL + cfg.AsteriscKona.Network = validAsteriscKonaNetwork +} + func validConfig(traceType types.TraceType) Config { cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validRollupRpc, validL2Rpc, validDatadir, traceType) if traceType == types.TraceTypeCannon || traceType == types.TraceTypePermissioned { @@ -58,6 +72,9 @@ func validConfig(traceType types.TraceType) Config { if traceType == types.TraceTypeAsterisc { applyValidConfigForAsterisc(&cfg) } + if traceType == types.TraceTypeAsteriscKona { + applyValidConfigForAsteriscKona(&cfg) + } return cfg } @@ -147,11 +164,12 @@ func TestCannonRequiredArgs(t *testing.T) { require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestMustNotSupplyBothCannonAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAllowSupplyingBothCannonAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + // Since the prestate baseURL might be inherited from the --prestate-urls option, allow overriding it with a specific prestate config := validConfig(traceType) config.CannonAbsolutePreState = validCannonAbsolutePreState config.CannonAbsolutePreStateBaseURL = validCannonAbsolutePreStateBaseURL - require.ErrorIs(t, config.Check(), ErrCannonAbsolutePreStateAndBaseURL) + require.NoError(t, config.Check()) }) t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { @@ -214,6 +232,18 @@ func TestCannonRequiredArgs(t *testing.T) { require.ErrorIs(t, cfg.Check(), ErrCannonNetworkUnknown) }) + t.Run(fmt.Sprintf("TestNetworkMayBeAnyChainID-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + cfg.Cannon.Network = "467294" + require.NoError(t, cfg.Check()) + }) + + t.Run(fmt.Sprintf("TestNetworkInvalidWhenNotEntirelyNumeric-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + cfg.Cannon.Network = "467294a" + require.ErrorIs(t, cfg.Check(), ErrCannonNetworkUnknown) + }) + t.Run(fmt.Sprintf("TestDebugInfoEnabled-%v", traceType), func(t *testing.T) { cfg := validConfig(traceType) require.True(t, cfg.Cannon.DebugInfo) @@ -258,11 +288,12 @@ func TestAsteriscRequiredArgs(t *testing.T) { require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestMustNotSupplyBothAsteriscAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAllowSupplingBothAsteriscAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + // Since the prestate base URL might be inherited from the --prestate-urls option, allow overriding it with a specific prestate config := validConfig(traceType) config.AsteriscAbsolutePreState = validAsteriscAbsolutePreState config.AsteriscAbsolutePreStateBaseURL = validAsteriscAbsolutePreStateBaseURL - require.ErrorIs(t, config.Check(), ErrAsteriscAbsolutePreStateAndBaseURL) + require.NoError(t, config.Check()) }) t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { @@ -332,6 +363,118 @@ func TestAsteriscRequiredArgs(t *testing.T) { } } +func TestAsteriscKonaRequiredArgs(t *testing.T) { + for _, traceType := range asteriscKonaTraceTypes { + traceType := traceType + + t.Run(fmt.Sprintf("TestAsteriscKonaBinRequired-%v", traceType), func(t *testing.T) { + config := validConfig(traceType) + config.AsteriscKona.VmBin = "" + require.ErrorIs(t, config.Check(), ErrMissingAsteriscKonaBin) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaServerRequired-%v", traceType), func(t *testing.T) { + config := validConfig(traceType) + config.AsteriscKona.Server = "" + require.ErrorIs(t, config.Check(), ErrMissingAsteriscKonaServer) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreStateOrBaseURLRequired-%v", traceType), func(t *testing.T) { + config := validConfig(traceType) + config.AsteriscKonaAbsolutePreState = "" + config.AsteriscKonaAbsolutePreStateBaseURL = nil + require.ErrorIs(t, config.Check(), ErrMissingAsteriscKonaAbsolutePreState) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreState-%v", traceType), func(t *testing.T) { + config := validConfig(traceType) + config.AsteriscKonaAbsolutePreState = validAsteriscKonaAbsolutePreState + config.AsteriscKonaAbsolutePreStateBaseURL = nil + require.NoError(t, config.Check()) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreStateBaseURL-%v", traceType), func(t *testing.T) { + config := validConfig(traceType) + config.AsteriscKonaAbsolutePreState = "" + config.AsteriscKonaAbsolutePreStateBaseURL = validAsteriscKonaAbsolutePreStateBaseURL + require.NoError(t, config.Check()) + }) + + t.Run(fmt.Sprintf("TestAllowSupplyingBothAsteriscKonaAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + // Since the prestate base URL might be inherited from the --prestate-urls option, allow overriding it with a specific prestate + config := validConfig(traceType) + config.AsteriscKonaAbsolutePreState = validAsteriscKonaAbsolutePreState + config.AsteriscKonaAbsolutePreStateBaseURL = validAsteriscKonaAbsolutePreStateBaseURL + require.NoError(t, config.Check()) + }) + + t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { + config := validConfig(traceType) + config.L2Rpc = "" + require.ErrorIs(t, config.Check(), ErrMissingL2Rpc) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaSnapshotFreq-%v", traceType), func(t *testing.T) { + t.Run("MustNotBeZero", func(t *testing.T) { + cfg := validConfig(traceType) + cfg.AsteriscKona.SnapshotFreq = 0 + require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscKonaSnapshotFreq) + }) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaInfoFreq-%v", traceType), func(t *testing.T) { + t.Run("MustNotBeZero", func(t *testing.T) { + cfg := validConfig(traceType) + cfg.AsteriscKona.InfoFreq = 0 + require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscKonaInfoFreq) + }) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaNetworkOrRollupConfigRequired-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + cfg.AsteriscKona.Network = "" + cfg.AsteriscKona.RollupConfigPath = "" + cfg.AsteriscKona.L2GenesisPath = "genesis.json" + require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscKonaRollupConfig) + }) + + t.Run(fmt.Sprintf("TestAsteriscKonaNetworkOrL2GenesisRequired-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + cfg.AsteriscKona.Network = "" + cfg.AsteriscKona.RollupConfigPath = "foo.json" + cfg.AsteriscKona.L2GenesisPath = "" + require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscKonaL2Genesis) + }) + + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + cfg.AsteriscKona.Network = validAsteriscKonaNetwork + cfg.AsteriscKona.RollupConfigPath = "foo.json" + cfg.AsteriscKona.L2GenesisPath = "" + require.ErrorIs(t, cfg.Check(), ErrAsteriscKonaNetworkAndRollupConfig) + }) + + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndL2Genesis-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + cfg.AsteriscKona.Network = validAsteriscKonaNetwork + cfg.AsteriscKona.RollupConfigPath = "" + cfg.AsteriscKona.L2GenesisPath = "foo.json" + require.ErrorIs(t, cfg.Check(), ErrAsteriscKonaNetworkAndL2Genesis) + }) + + t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + cfg.AsteriscKona.Network = "unknown" + require.ErrorIs(t, cfg.Check(), ErrAsteriscKonaNetworkUnknown) + }) + + t.Run(fmt.Sprintf("TestDebugInfoDisabled-%v", traceType), func(t *testing.T) { + cfg := validConfig(traceType) + require.False(t, cfg.AsteriscKona.DebugInfo) + }) + } +} + func TestDatadirRequired(t *testing.T) { config := validConfig(types.TraceTypeAlphabet) config.Datadir = "" diff --git a/op-challenger/flags/flags.go b/op-challenger/flags/flags.go index 38e59d350cec4..3face06bdc806 100644 --- a/op-challenger/flags/flags.go +++ b/op-challenger/flags/flags.go @@ -32,6 +32,7 @@ func prefixEnvVars(name string) []string { } var ( + faultDisputeVMs = []types.TraceType{types.TraceTypeCannon, types.TraceTypeAsterisc, types.TraceTypeAsteriscKona} // Required Flags L1EthRpcFlag = &cli.StringFlag{ Name: "l1-eth-rpc", @@ -64,7 +65,7 @@ var ( Name: "trace-type", Usage: "The trace types to support. Valid options: " + openum.EnumString(types.TraceTypes), EnvVars: prefixEnvVars("TRACE_TYPE"), - Value: cli.NewStringSlice(types.TraceTypeCannon.String()), + Value: cli.NewStringSlice(types.TraceTypeCannon.String(), types.TraceTypeAsteriscKona.String()), } DatadirFlag = &cli.StringFlag{ Name: "datadir", @@ -100,11 +101,28 @@ var ( Usage: "List of addresses to claim bonds for, in addition to the configured transaction sender", EnvVars: prefixEnvVars("ADDITIONAL_BOND_CLAIMANTS"), } + PreStatesURLFlag = NewVMFlag("prestates-url", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, traceTypeInfo string) cli.Flag { + return &cli.StringFlag{ + Name: name, + Usage: "Base URL to absolute prestates to use when generating trace data. " + + "Prestates in this directory should be name as .bin.gz .json.gz or .json " + + traceTypeInfo, + EnvVars: envVars, + } + }) CannonNetworkFlag = &cli.StringFlag{ Name: "cannon-network", Usage: fmt.Sprintf("Deprecated: Use %v instead", flags.NetworkFlagName), EnvVars: prefixEnvVars("CANNON_NETWORK"), } + CannonL2CustomFlag = &cli.BoolFlag{ + Name: "cannon-l2-custom", + Usage: "Notify the op-program host that the L2 chain uses custom config to be loaded via the preimage oracle. " + + "WARNING: This is incompatible with on-chain testing and must only be used for testing purposes.", + EnvVars: prefixEnvVars("CANNON_L2_CUSTOM"), + Value: false, + Hidden: true, + } CannonRollupConfigFlag = &cli.StringFlag{ Name: "cannon-rollup-config", Usage: "Rollup chain parameters (cannon trace type only)", @@ -130,12 +148,6 @@ var ( Usage: "Path to absolute prestate to use when generating trace data (cannon trace type only)", EnvVars: prefixEnvVars("CANNON_PRESTATE"), } - CannonPreStatesURLFlag = &cli.StringFlag{ - Name: "cannon-prestates-url", - Usage: "Base URL to absolute prestates to use when generating trace data. " + - "Prestates in this directory should be name as .json (cannon trace type only)", - EnvVars: prefixEnvVars("CANNON_PRESTATES_URL"), - } CannonL2Flag = &cli.StringFlag{ Name: "cannon-l2", Usage: fmt.Sprintf("Deprecated: Use %v instead", L2EthRpcFlag.Name), @@ -193,18 +205,6 @@ var ( Usage: "Path to absolute prestate to use when generating trace data (asterisc-kona trace type only)", EnvVars: prefixEnvVars("ASTERISC_KONA_PRESTATE"), } - AsteriscPreStatesURLFlag = &cli.StringFlag{ - Name: "asterisc-prestates-url", - Usage: "Base URL to absolute prestates to use when generating trace data. " + - "Prestates in this directory should be name as .json (asterisc trace type only)", - EnvVars: prefixEnvVars("ASTERISC_PRESTATES_URL"), - } - AsteriscKonaPreStatesURLFlag = &cli.StringFlag{ - Name: "asterisc-kona-prestates-url", - Usage: "Base URL to absolute prestates to use when generating trace data. " + - "Prestates in this directory should be name as .json (asterisc-kona trace type only)", - EnvVars: prefixEnvVars("ASTERISC_KONA_PRESTATES_URL"), - } AsteriscSnapshotFreqFlag = &cli.UintFlag{ Name: "asterisc-snapshot-freq", Usage: "Frequency of asterisc snapshots to generate in VM steps (asterisc trace type only)", @@ -257,12 +257,12 @@ var optionalFlags = []cli.Flag{ AdditionalBondClaimants, GameAllowlistFlag, CannonNetworkFlag, + CannonL2CustomFlag, CannonRollupConfigFlag, CannonL2GenesisFlag, CannonBinFlag, CannonServerFlag, CannonPreStateFlag, - CannonPreStatesURLFlag, CannonL2Flag, CannonSnapshotFreqFlag, CannonInfoFreqFlag, @@ -274,8 +274,6 @@ var optionalFlags = []cli.Flag{ AsteriscKonaServerFlag, AsteriscPreStateFlag, AsteriscKonaPreStateFlag, - AsteriscPreStatesURLFlag, - AsteriscKonaPreStatesURLFlag, AsteriscSnapshotFreqFlag, AsteriscInfoFreqFlag, GameWindowFlag, @@ -285,6 +283,7 @@ var optionalFlags = []cli.Flag{ func init() { optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, PreStatesURLFlag.Flags()...) optionalFlags = append(optionalFlags, txmgr.CLIFlagsWithDefaults(EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...) @@ -306,14 +305,17 @@ func CheckCannonFlags(ctx *cli.Context) error { CannonNetworkFlag.Name, flags.NetworkFlagName, CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name) } if ctx.IsSet(flags.NetworkFlagName) && - (ctx.IsSet(CannonRollupConfigFlag.Name) || ctx.IsSet(CannonL2GenesisFlag.Name)) { - return fmt.Errorf("flag %v can not be used with %v and %v", - flags.NetworkFlagName, CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name) + (ctx.IsSet(CannonRollupConfigFlag.Name) || ctx.IsSet(CannonL2GenesisFlag.Name) || ctx.Bool(CannonL2CustomFlag.Name)) { + return fmt.Errorf("flag %v can not be used with %v, %v or %v", + flags.NetworkFlagName, CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name, CannonL2CustomFlag.Name) } if ctx.IsSet(CannonNetworkFlag.Name) && - (ctx.IsSet(CannonRollupConfigFlag.Name) || ctx.IsSet(CannonL2GenesisFlag.Name)) { - return fmt.Errorf("flag %v can not be used with %v and %v", - CannonNetworkFlag.Name, CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name) + (ctx.IsSet(CannonRollupConfigFlag.Name) || ctx.IsSet(CannonL2GenesisFlag.Name) || ctx.Bool(CannonL2CustomFlag.Name)) { + return fmt.Errorf("flag %v can not be used with %v, %v or %v", + CannonNetworkFlag.Name, CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name, CannonL2CustomFlag.Name) + } + if ctx.Bool(CannonL2CustomFlag.Name) && !(ctx.IsSet(CannonRollupConfigFlag.Name) && ctx.IsSet(CannonL2GenesisFlag.Name)) { + return fmt.Errorf("flag %v and %v must be set when %v is true", CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name, CannonL2CustomFlag.Name) } if !ctx.IsSet(CannonBinFlag.Name) { return fmt.Errorf("flag %s is required", CannonBinFlag.Name) @@ -321,8 +323,8 @@ func CheckCannonFlags(ctx *cli.Context) error { if !ctx.IsSet(CannonServerFlag.Name) { return fmt.Errorf("flag %s is required", CannonServerFlag.Name) } - if !ctx.IsSet(CannonPreStateFlag.Name) && !ctx.IsSet(CannonPreStatesURLFlag.Name) { - return fmt.Errorf("flag %s or %s is required", CannonPreStatesURLFlag.Name, CannonPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeCannon) && !ctx.IsSet(CannonPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.DefaultName(), CannonPreStateFlag.Name) } return nil } @@ -360,8 +362,8 @@ func CheckAsteriscFlags(ctx *cli.Context) error { if !ctx.IsSet(AsteriscServerFlag.Name) { return fmt.Errorf("flag %s is required", AsteriscServerFlag.Name) } - if !ctx.IsSet(AsteriscPreStateFlag.Name) && !ctx.IsSet(AsteriscPreStatesURLFlag.Name) { - return fmt.Errorf("flag %s or %s is required", AsteriscPreStatesURLFlag.Name, AsteriscPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeAsterisc) && !ctx.IsSet(AsteriscPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.DefaultName(), AsteriscPreStateFlag.Name) } return nil } @@ -373,8 +375,8 @@ func CheckAsteriscKonaFlags(ctx *cli.Context) error { if !ctx.IsSet(AsteriscKonaServerFlag.Name) { return fmt.Errorf("flag %s is required", AsteriscKonaServerFlag.Name) } - if !ctx.IsSet(AsteriscKonaPreStateFlag.Name) && !ctx.IsSet(AsteriscKonaPreStatesURLFlag.Name) { - return fmt.Errorf("flag %s or %s is required", AsteriscKonaPreStatesURLFlag.Name, AsteriscKonaPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeAsteriscKona) && !ctx.IsSet(AsteriscKonaPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.DefaultName(), AsteriscKonaPreStateFlag.Name) } return nil } @@ -513,29 +515,29 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro claimants = append(claimants, claimant) } } - var cannonPrestatesURL *url.URL - if ctx.IsSet(CannonPreStatesURLFlag.Name) { - parsed, err := url.Parse(ctx.String(CannonPreStatesURLFlag.Name)) + var cannonPreStatesURL *url.URL + if PreStatesURLFlag.IsSet(ctx, types.TraceTypeCannon) { + val := PreStatesURLFlag.String(ctx, types.TraceTypeCannon) + cannonPreStatesURL, err = url.Parse(val) if err != nil { - return nil, fmt.Errorf("invalid cannon pre states url (%v): %w", ctx.String(CannonPreStatesURLFlag.Name), err) + return nil, fmt.Errorf("invalid %v (%v): %w", PreStatesURLFlag.SourceFlagName(ctx, types.TraceTypeCannon), val, err) } - cannonPrestatesURL = parsed } var asteriscPreStatesURL *url.URL - if ctx.IsSet(AsteriscPreStatesURLFlag.Name) { - parsed, err := url.Parse(ctx.String(AsteriscPreStatesURLFlag.Name)) + if PreStatesURLFlag.IsSet(ctx, types.TraceTypeAsterisc) { + val := PreStatesURLFlag.String(ctx, types.TraceTypeAsterisc) + asteriscPreStatesURL, err = url.Parse(val) if err != nil { - return nil, fmt.Errorf("invalid asterisc pre states url (%v): %w", ctx.String(AsteriscPreStatesURLFlag.Name), err) + return nil, fmt.Errorf("invalid %v (%v): %w", PreStatesURLFlag.SourceFlagName(ctx, types.TraceTypeAsterisc), val, err) } - asteriscPreStatesURL = parsed } var asteriscKonaPreStatesURL *url.URL - if ctx.IsSet(AsteriscKonaPreStatesURLFlag.Name) { - parsed, err := url.Parse(ctx.String(AsteriscKonaPreStatesURLFlag.Name)) + if PreStatesURLFlag.IsSet(ctx, types.TraceTypeAsteriscKona) { + val := PreStatesURLFlag.String(ctx, types.TraceTypeAsteriscKona) + asteriscKonaPreStatesURL, err = url.Parse(val) if err != nil { - return nil, fmt.Errorf("invalid asterisc-kona pre states url (%v): %w", ctx.String(AsteriscKonaPreStatesURLFlag.Name), err) + return nil, fmt.Errorf("invalid %v (%v): %w", PreStatesURLFlag.SourceFlagName(ctx, types.TraceTypeAsteriscKona), val, err) } - asteriscKonaPreStatesURL = parsed } l2Rpc, err := getL2Rpc(ctx, logger) if err != nil { @@ -573,6 +575,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro VmBin: ctx.String(CannonBinFlag.Name), Server: ctx.String(CannonServerFlag.Name), Network: cannonNetwork, + L2Custom: ctx.Bool(CannonL2CustomFlag.Name), RollupConfigPath: ctx.String(CannonRollupConfigFlag.Name), L2GenesisPath: ctx.String(CannonL2GenesisFlag.Name), SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), @@ -581,7 +584,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro BinarySnapshots: true, }, CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), - CannonAbsolutePreStateBaseURL: cannonPrestatesURL, + CannonAbsolutePreStateBaseURL: cannonPreStatesURL, Datadir: ctx.String(DatadirFlag.Name), Asterisc: vm.Config{ VmType: types.TraceTypeAsterisc, @@ -595,6 +598,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro L2GenesisPath: ctx.String(AsteriscL2GenesisFlag.Name), SnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), + BinarySnapshots: true, }, AsteriscAbsolutePreState: ctx.String(AsteriscPreStateFlag.Name), AsteriscAbsolutePreStateBaseURL: asteriscPreStatesURL, @@ -610,6 +614,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro L2GenesisPath: ctx.String(AsteriscL2GenesisFlag.Name), SnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), + BinarySnapshots: true, }, AsteriscKonaAbsolutePreState: ctx.String(AsteriscKonaPreStateFlag.Name), AsteriscKonaAbsolutePreStateBaseURL: asteriscKonaPreStatesURL, diff --git a/op-challenger/flags/vm_flag.go b/op-challenger/flags/vm_flag.go new file mode 100644 index 0000000000000..8464f7e5fdd47 --- /dev/null +++ b/op-challenger/flags/vm_flag.go @@ -0,0 +1,70 @@ +package flags + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/urfave/cli/v2" +) + +type FlagCreator func(name string, envVars []string, traceTypeInfo string) cli.Flag + +// VMFlag defines a set of flags to set a VM specific option. Provides a flag to set the default plus flags to +// override the default on a per VM basis. +type VMFlag struct { + vms []types.TraceType + name string + envVarPrefix string + flagCreator FlagCreator +} + +func NewVMFlag(name string, envVarPrefix string, vms []types.TraceType, flagCreator FlagCreator) *VMFlag { + return &VMFlag{ + name: name, + envVarPrefix: envVarPrefix, + flagCreator: flagCreator, + vms: vms, + } +} + +func (f *VMFlag) Flags() []cli.Flag { + flags := make([]cli.Flag, 0, len(f.vms)) + // Default + defaultEnvVar := opservice.FlagNameToEnvVarName(f.name, f.envVarPrefix) + flags = append(flags, f.flagCreator(f.name, []string{defaultEnvVar}, "")) + for _, vm := range f.vms { + name := f.flagName(vm) + envVar := opservice.FlagNameToEnvVarName(name, f.envVarPrefix) + flags = append(flags, f.flagCreator(name, []string{envVar}, fmt.Sprintf("(%v trace type only)", vm))) + } + return flags +} + +func (f *VMFlag) DefaultName() string { + return f.name +} + +func (f *VMFlag) IsSet(ctx *cli.Context, vm types.TraceType) bool { + return ctx.IsSet(f.flagName(vm)) || ctx.IsSet(f.name) +} + +func (f *VMFlag) String(ctx *cli.Context, vm types.TraceType) string { + val := ctx.String(f.flagName(vm)) + if val == "" { + val = ctx.String(f.name) + } + return val +} + +func (f *VMFlag) SourceFlagName(ctx *cli.Context, vm types.TraceType) string { + vmFlag := f.flagName(vm) + if ctx.IsSet(vmFlag) { + return vmFlag + } + return f.name +} + +func (f *VMFlag) flagName(vm types.TraceType) string { + return fmt.Sprintf("%v-%v", vm, f.name) +} diff --git a/op-challenger/game/fault/register.go b/op-challenger/game/fault/register.go index 38957be0ce95d..17874e7ddde8b 100644 --- a/op-challenger/game/fault/register.go +++ b/op-challenger/game/fault/register.go @@ -68,13 +68,13 @@ func RegisterGameTypes( var registerTasks []*RegisterTask if cfg.TraceTypeEnabled(faultTypes.TraceTypeCannon) { - registerTasks = append(registerTasks, NewCannonRegisterTask(faultTypes.CannonGameType, cfg, m, vm.NewOpProgramServerExecutor())) + registerTasks = append(registerTasks, NewCannonRegisterTask(faultTypes.CannonGameType, cfg, m, vm.NewOpProgramServerExecutor(logger))) } if cfg.TraceTypeEnabled(faultTypes.TraceTypePermissioned) { - registerTasks = append(registerTasks, NewCannonRegisterTask(faultTypes.PermissionedGameType, cfg, m, vm.NewOpProgramServerExecutor())) + registerTasks = append(registerTasks, NewCannonRegisterTask(faultTypes.PermissionedGameType, cfg, m, vm.NewOpProgramServerExecutor(logger))) } if cfg.TraceTypeEnabled(faultTypes.TraceTypeAsterisc) { - registerTasks = append(registerTasks, NewAsteriscRegisterTask(faultTypes.AsteriscGameType, cfg, m, vm.NewOpProgramServerExecutor())) + registerTasks = append(registerTasks, NewAsteriscRegisterTask(faultTypes.AsteriscGameType, cfg, m, vm.NewOpProgramServerExecutor(logger))) } if cfg.TraceTypeEnabled(faultTypes.TraceTypeAsteriscKona) { registerTasks = append(registerTasks, NewAsteriscKonaRegisterTask(faultTypes.AsteriscKonaGameType, cfg, m, vm.NewKonaExecutor())) diff --git a/op-challenger/game/fault/register_task.go b/op-challenger/game/fault/register_task.go index e2fcbc60c428c..1c1db190bd269 100644 --- a/op-challenger/game/fault/register_task.go +++ b/op-challenger/game/fault/register_task.go @@ -256,7 +256,7 @@ func (e *RegisterTask) Register( } return NewGamePlayer(ctx, systemClock, l1Clock, logger, m, dir, game.Proxy, txSender, contract, syncValidator, validators, creator, l1HeaderSource, selective, claimants) } - err := registerOracle(ctx, m, oracles, gameFactory, caller, e.gameType) + err := registerOracle(ctx, logger, m, oracles, gameFactory, caller, e.gameType) if err != nil { return err } @@ -269,11 +269,15 @@ func (e *RegisterTask) Register( return nil } -func registerOracle(ctx context.Context, m metrics.Metricer, oracles OracleRegistry, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller, gameType faultTypes.GameType) error { +func registerOracle(ctx context.Context, logger log.Logger, m metrics.Metricer, oracles OracleRegistry, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller, gameType faultTypes.GameType) error { implAddr, err := gameFactory.GetGameImpl(ctx, gameType) if err != nil { return fmt.Errorf("failed to load implementation for game type %v: %w", gameType, err) } + if implAddr == (common.Address{}) { + logger.Warn("No game implementation set for game type", "gameType", gameType) + return nil + } contract, err := contracts.NewFaultDisputeGameContract(ctx, m, implAddr, caller) if err != nil { return fmt.Errorf("failed to create fault dispute game contracts: %w", err) diff --git a/op-challenger/game/fault/register_task_test.go b/op-challenger/game/fault/register_task_test.go new file mode 100644 index 0000000000000..f09c3e79fe34c --- /dev/null +++ b/op-challenger/game/fault/register_task_test.go @@ -0,0 +1,71 @@ +package fault + +import ( + "context" + "testing" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/game/registry" + "github.com/ethereum-optimism/optimism/op-challenger/metrics" + "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/sources/batching/test" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestRegisterOracle_MissingGameImpl(t *testing.T) { + gameFactoryAddr := common.Address{0xaa} + rpc := test.NewAbiBasedRpc(t, gameFactoryAddr, snapshots.LoadDisputeGameFactoryABI()) + m := metrics.NoopMetrics + caller := batching.NewMultiCaller(rpc, batching.DefaultBatchSize) + gameFactory := contracts.NewDisputeGameFactoryContract(m, gameFactoryAddr, caller) + + logger, logs := testlog.CaptureLogger(t, log.LvlInfo) + oracles := registry.NewOracleRegistry() + gameType := faultTypes.CannonGameType + + rpc.SetResponse(gameFactoryAddr, "gameImpls", rpcblock.Latest, []interface{}{gameType}, []interface{}{common.Address{}}) + + err := registerOracle(context.Background(), logger, m, oracles, gameFactory, caller, gameType) + require.NoError(t, err) + require.NotNil(t, logs.FindLog( + testlog.NewMessageFilter("No game implementation set for game type"), + testlog.NewAttributesFilter("gameType", gameType.String()))) +} + +func TestRegisterOracle_AddsOracle(t *testing.T) { + gameFactoryAddr := common.Address{0xaa} + gameImplAddr := common.Address{0xbb} + vmAddr := common.Address{0xcc} + oracleAddr := common.Address{0xdd} + rpc := test.NewAbiBasedRpc(t, gameFactoryAddr, snapshots.LoadDisputeGameFactoryABI()) + rpc.AddContract(gameImplAddr, snapshots.LoadFaultDisputeGameABI()) + rpc.AddContract(vmAddr, snapshots.LoadMIPSABI()) + rpc.AddContract(oracleAddr, snapshots.LoadPreimageOracleABI()) + m := metrics.NoopMetrics + caller := batching.NewMultiCaller(rpc, batching.DefaultBatchSize) + gameFactory := contracts.NewDisputeGameFactoryContract(m, gameFactoryAddr, caller) + + logger := testlog.Logger(t, log.LvlInfo) + oracles := registry.NewOracleRegistry() + gameType := faultTypes.CannonGameType + + // Use the latest v1 of these contracts. Doesn't have to be an exact match for the version. + rpc.SetResponse(gameImplAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) + rpc.SetResponse(oracleAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) + + rpc.SetResponse(gameFactoryAddr, "gameImpls", rpcblock.Latest, []interface{}{gameType}, []interface{}{gameImplAddr}) + rpc.SetResponse(gameImplAddr, "vm", rpcblock.Latest, []interface{}{}, []interface{}{vmAddr}) + rpc.SetResponse(vmAddr, "oracle", rpcblock.Latest, []interface{}{}, []interface{}{oracleAddr}) + + err := registerOracle(context.Background(), logger, m, oracles, gameFactory, caller, gameType) + require.NoError(t, err) + registered := oracles.Oracles() + require.Len(t, registered, 1) + require.Equal(t, oracleAddr, registered[0].Addr()) +} diff --git a/op-challenger/game/fault/trace/asterisc/provider.go b/op-challenger/game/fault/trace/asterisc/provider.go index 0916cd29397d9..c622b6ed9dd1a 100644 --- a/op-challenger/game/fault/trace/asterisc/provider.go +++ b/op-challenger/game/fault/trace/asterisc/provider.go @@ -168,7 +168,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi logger: logger, dir: dir, prestate: cfg.AsteriscAbsolutePreState, - generator: vm.NewExecutor(logger, m, cfg.Asterisc, vm.NewOpProgramServerExecutor(), cfg.AsteriscAbsolutePreState, localInputs), + generator: vm.NewExecutor(logger, m, cfg.Asterisc, vm.NewOpProgramServerExecutor(logger), cfg.AsteriscAbsolutePreState, localInputs), gameDepth: gameDepth, preimageLoader: utils.NewPreimageLoader(func() (utils.PreimageSource, error) { return kvstore.NewDiskKV(logger, vm.PreimageDir(dir), kvtypes.DataFormatFile) diff --git a/op-challenger/game/fault/trace/cannon/provider.go b/op-challenger/game/fault/trace/cannon/provider.go index cca2cf0e484e4..7cffccfe84337 100644 --- a/op-challenger/game/fault/trace/cannon/provider.go +++ b/op-challenger/game/fault/trace/cannon/provider.go @@ -167,7 +167,7 @@ func NewTraceProviderForTest(logger log.Logger, m vm.Metricer, cfg *config.Confi logger: logger, dir: dir, prestate: cfg.CannonAbsolutePreState, - generator: vm.NewExecutor(logger, m, cfg.Cannon, vm.NewOpProgramServerExecutor(), cfg.CannonAbsolutePreState, localInputs), + generator: vm.NewExecutor(logger, m, cfg.Cannon, vm.NewOpProgramServerExecutor(logger), cfg.CannonAbsolutePreState, localInputs), gameDepth: gameDepth, preimageLoader: utils.NewPreimageLoader(func() (utils.PreimageSource, error) { return kvstore.NewDiskKV(logger, vm.PreimageDir(dir), kvtypes.DataFormatFile) diff --git a/op-challenger/game/fault/trace/prestates/multi.go b/op-challenger/game/fault/trace/prestates/multi.go index 020dfc40deaf1..a769f662c1886 100644 --- a/op-challenger/game/fault/trace/prestates/multi.go +++ b/op-challenger/game/fault/trace/prestates/multi.go @@ -71,18 +71,11 @@ func (m *MultiPrestateProvider) fetchPrestate(ctx context.Context, hash common.H prestateUrl := m.baseUrl.JoinPath(hash.Hex() + fileType) tCtx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() - req, err := http.NewRequestWithContext(tCtx, "GET", prestateUrl.String(), nil) + in, err := m.fetch(tCtx, prestateUrl) if err != nil { - return fmt.Errorf("failed to create prestate request: %w", err) - } - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("failed to fetch prestate from %v: %w", prestateUrl, err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("%w from url %v: status %v", ErrPrestateUnavailable, prestateUrl, resp.StatusCode) + return err } + defer in.Close() tmpFile := dest + ".tmp" + fileType // Preserve the file type extension so state decoding is applied correctly out, err := ioutil.NewAtomicWriter(tmpFile, 0o644) if err != nil { @@ -92,7 +85,7 @@ func (m *MultiPrestateProvider) fetchPrestate(ctx context.Context, hash common.H // If errors occur, try to clean up without renaming the file into its final destination as Close() would do _ = out.Abort() }() - if _, err := io.Copy(out, resp.Body); err != nil { + if _, err := io.Copy(out, in); err != nil { return fmt.Errorf("failed to write file %v: %w", dest, err) } if err := out.Close(); err != nil { @@ -110,3 +103,27 @@ func (m *MultiPrestateProvider) fetchPrestate(ctx context.Context, hash common.H } return nil } + +func (m *MultiPrestateProvider) fetch(ctx context.Context, prestateUrl *url.URL) (io.ReadCloser, error) { + if prestateUrl.Scheme == "file" { + path := prestateUrl.Path + in, err := os.OpenFile(path, os.O_RDONLY, 0o644) + if errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("%w unavailable from path %v", ErrPrestateUnavailable, path) + } + return in, err + } + req, err := http.NewRequestWithContext(ctx, "GET", prestateUrl.String(), nil) + if err != nil { + return nil, fmt.Errorf("failed to create prestate request: %w", err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to fetch prestate from %v: %w", prestateUrl, err) + } + if resp.StatusCode != http.StatusOK { + _ = resp.Body.Close() // Not returning the body so make a best effort to close it now. + return nil, fmt.Errorf("%w from url %v: status %v", ErrPrestateUnavailable, prestateUrl, resp.StatusCode) + } + return resp.Body, nil +} diff --git a/op-challenger/game/fault/trace/prestates/multi_test.go b/op-challenger/game/fault/trace/prestates/multi_test.go index e8f79dd2fcc8d..5df78654b197c 100644 --- a/op-challenger/game/fault/trace/prestates/multi_test.go +++ b/op-challenger/game/fault/trace/prestates/multi_test.go @@ -18,11 +18,17 @@ import ( "github.com/stretchr/testify/require" ) -func TestDownloadPrestate(t *testing.T) { +func TestDownloadPrestateHTTP(t *testing.T) { for _, ext := range supportedFileTypes { t.Run(ext, func(t *testing.T) { dir := t.TempDir() - server := prestateHTTPServer(ext) + mkContent := func(path string) []byte { + // Large enough to be bigger than a single write buffer. + out := make([]byte, 16192) + copy(out, path) + return out + } + server := prestateHTTPServer(ext, mkContent) defer server.Close() hash := common.Hash{0xaa} provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash}) @@ -33,7 +39,29 @@ func TestDownloadPrestate(t *testing.T) { defer in.Close() content, err := io.ReadAll(in) require.NoError(t, err) - require.Equal(t, "/"+hash.Hex()+ext, string(content)) + require.Equal(t, mkContent("/"+hash.Hex()+ext), content) + }) + } +} + +func TestDownloadPrestateFile(t *testing.T) { + for _, ext := range supportedFileTypes { + t.Run(ext, func(t *testing.T) { + sourceDir := t.TempDir() + dir := t.TempDir() + hash := common.Hash{0xaa} + sourcePath := filepath.Join(sourceDir, hash.Hex()+ext) + expectedContent := "/" + hash.Hex() + ext + require.NoError(t, os.WriteFile(sourcePath, []byte(expectedContent), 0600)) + provider := NewMultiPrestateProvider(parseURL(t, "file:"+sourceDir), dir, &stubStateConverter{hash: hash}) + path, err := provider.PrestatePath(context.Background(), hash) + require.NoError(t, err) + in, err := os.Open(path) + require.NoError(t, err) + defer in.Close() + content, err := io.ReadAll(in) + require.NoError(t, err) + require.Equal(t, expectedContent, string(content)) }) } } @@ -43,7 +71,7 @@ func TestCreateDirectory(t *testing.T) { t.Run(ext, func(t *testing.T) { dir := t.TempDir() dir = filepath.Join(dir, "test") - server := prestateHTTPServer(ext) + server := prestateHTTPServer(ext, func(path string) []byte { return []byte(path) }) defer server.Close() hash := common.Hash{0xaa} provider := NewMultiPrestateProvider(parseURL(t, server.URL), dir, &stubStateConverter{hash: hash}) @@ -166,10 +194,10 @@ func parseURL(t *testing.T, str string) *url.URL { return parsed } -func prestateHTTPServer(ext string) *httptest.Server { +func prestateHTTPServer(ext string, content func(path string) []byte) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, ext) { - _, _ = w.Write([]byte(r.URL.Path)) + _, _ = w.Write(content(r.URL.Path)) } else { w.WriteHeader(http.StatusNotFound) } diff --git a/op-challenger/game/fault/trace/prestates/source.go b/op-challenger/game/fault/trace/prestates/source.go index a4c9839ad1e5d..cc6cdcf2bfe98 100644 --- a/op-challenger/game/fault/trace/prestates/source.go +++ b/op-challenger/game/fault/trace/prestates/source.go @@ -7,9 +7,9 @@ import ( ) func NewPrestateSource(baseURL *url.URL, path string, localDir string, stateConverter vm.StateConverter) PrestateSource { - if baseURL != nil { - return NewMultiPrestateProvider(baseURL, localDir, stateConverter) - } else { + if path != "" { return NewSinglePrestateSource(path) + } else { + return NewMultiPrestateProvider(baseURL, localDir, stateConverter) } } diff --git a/op-challenger/game/fault/trace/prestates/source_test.go b/op-challenger/game/fault/trace/prestates/source_test.go new file mode 100644 index 0000000000000..75ebaa76decd5 --- /dev/null +++ b/op-challenger/game/fault/trace/prestates/source_test.go @@ -0,0 +1,15 @@ +package prestates + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreferSinglePrestate(t *testing.T) { + uri, err := url.Parse("http://localhost") + require.NoError(t, err) + source := NewPrestateSource(uri, "/tmp/path.json", t.TempDir(), nil) + require.IsType(t, &SinglePrestateSource{}, source) +} diff --git a/op-challenger/game/fault/trace/vm/executor.go b/op-challenger/game/fault/trace/vm/executor.go index 9e2cd0d29e93e..524124a6eab5a 100644 --- a/op-challenger/game/fault/trace/vm/executor.go +++ b/op-challenger/game/fault/trace/vm/executor.go @@ -41,6 +41,7 @@ type Config struct { L2 string Server string // Path to the executable that provides the pre-image oracle server Network string + L2Custom bool RollupConfigPath string L2GenesisPath string } diff --git a/op-challenger/game/fault/trace/vm/executor_test.go b/op-challenger/game/fault/trace/vm/executor_test.go index ff4a8ba7e1ab2..13146d8f263b6 100644 --- a/op-challenger/game/fault/trace/vm/executor_test.go +++ b/op-challenger/game/fault/trace/vm/executor_test.go @@ -41,7 +41,7 @@ func TestGenerateProof(t *testing.T) { } captureExec := func(t *testing.T, cfg Config, proofAt uint64) (string, string, map[string]string) { m := &stubVmMetrics{} - executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, cfg, NewOpProgramServerExecutor(), prestate, inputs) + executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, cfg, NewOpProgramServerExecutor(testlog.Logger(t, log.LvlInfo)), prestate, inputs) executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64, binary bool) (string, error) { return input, nil } diff --git a/op-challenger/game/fault/trace/vm/kona_server_executor.go b/op-challenger/game/fault/trace/vm/kona_server_executor.go index a8be1c7bc5620..ffb74d9a31b88 100644 --- a/op-challenger/game/fault/trace/vm/kona_server_executor.go +++ b/op-challenger/game/fault/trace/vm/kona_server_executor.go @@ -9,8 +9,7 @@ import ( ) type KonaExecutor struct { - nativeMode bool - clientBinPath string + nativeMode bool } var _ OracleServerExecutor = (*KonaExecutor)(nil) @@ -19,8 +18,8 @@ func NewKonaExecutor() *KonaExecutor { return &KonaExecutor{nativeMode: false} } -func NewNativeKonaExecutor(clientBinPath string) *KonaExecutor { - return &KonaExecutor{nativeMode: true, clientBinPath: clientBinPath} +func NewNativeKonaExecutor() *KonaExecutor { + return &KonaExecutor{nativeMode: true} } func (s *KonaExecutor) OracleCommand(cfg Config, dataDir string, inputs utils.LocalGameInputs) ([]string, error) { @@ -34,11 +33,10 @@ func (s *KonaExecutor) OracleCommand(cfg Config, dataDir string, inputs utils.Lo "--l2-output-root", inputs.L2OutputRoot.Hex(), "--l2-claim", inputs.L2Claim.Hex(), "--l2-block-number", inputs.L2BlockNumber.Text(10), - "-v", } if s.nativeMode { - args = append(args, "--exec", s.clientBinPath) + args = append(args, "--native") } else { args = append(args, "--server") args = append(args, "--data-dir", dataDir) diff --git a/op-challenger/game/fault/trace/vm/op_program_server_executor.go b/op-challenger/game/fault/trace/vm/op_program_server_executor.go index 11044307e9af8..c353b1a4a1628 100644 --- a/op-challenger/game/fault/trace/vm/op_program_server_executor.go +++ b/op-challenger/game/fault/trace/vm/op_program_server_executor.go @@ -1,16 +1,20 @@ package vm import ( + "context" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum/go-ethereum/log" ) type OpProgramServerExecutor struct { + logger log.Logger } var _ OracleServerExecutor = (*OpProgramServerExecutor)(nil) -func NewOpProgramServerExecutor() *OpProgramServerExecutor { - return &OpProgramServerExecutor{} +func NewOpProgramServerExecutor(logger log.Logger) *OpProgramServerExecutor { + return &OpProgramServerExecutor{logger: logger} } func (s *OpProgramServerExecutor) OracleCommand(cfg Config, dataDir string, inputs utils.LocalGameInputs) ([]string, error) { @@ -35,5 +39,23 @@ func (s *OpProgramServerExecutor) OracleCommand(cfg Config, dataDir string, inpu if cfg.L2GenesisPath != "" { args = append(args, "--l2.genesis", cfg.L2GenesisPath) } + var logLevel string + if s.logger.Enabled(context.Background(), log.LevelTrace) { + logLevel = "TRACE" + } else if s.logger.Enabled(context.Background(), log.LevelDebug) { + logLevel = "DEBUG" + } else if s.logger.Enabled(context.Background(), log.LevelInfo) { + logLevel = "INFO" + } else if s.logger.Enabled(context.Background(), log.LevelWarn) { + logLevel = "WARN" + } else if s.logger.Enabled(context.Background(), log.LevelError) { + logLevel = "ERROR" + } else { + logLevel = "CRIT" + } + args = append(args, "--log.level", logLevel) + if cfg.L2Custom { + args = append(args, "--l2.custom") + } return args, nil } diff --git a/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go b/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go index ff17209d63241..8cf69534c7b0c 100644 --- a/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go +++ b/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go @@ -1,98 +1,131 @@ package vm import ( + "fmt" + "log/slog" "math/big" - "slices" "testing" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) func TestOpProgramFillHostCommand(t *testing.T) { dir := "mockdir" - cfg := Config{ - L1: "http://localhost:8888", - L1Beacon: "http://localhost:9000", - L2: "http://localhost:9999", - Server: "./bin/mockserver", - } - inputs := utils.LocalGameInputs{ - L1Head: common.Hash{0x11}, - L2Head: common.Hash{0x22}, - L2OutputRoot: common.Hash{0x33}, - L2Claim: common.Hash{0x44}, - L2BlockNumber: big.NewInt(3333), - } - validateStandard := func(t *testing.T, args []string) { - require.True(t, slices.Contains(args, "--server")) - require.True(t, slices.Contains(args, "--l1")) - require.True(t, slices.Contains(args, "--l1.beacon")) - require.True(t, slices.Contains(args, "--l2")) - require.True(t, slices.Contains(args, "--datadir")) - require.True(t, slices.Contains(args, "--l1.head")) - require.True(t, slices.Contains(args, "--l2.head")) - require.True(t, slices.Contains(args, "--l2.outputroot")) - require.True(t, slices.Contains(args, "--l2.claim")) - require.True(t, slices.Contains(args, "--l2.blocknumber")) + toPairs := func(args []string) map[string]string { + pairs := make(map[string]string, len(args)/2) + for i := 0; i < len(args); i += 2 { + // l2.custom is a boolean flag so can't accept a value after a space + if args[i] == "--l2.custom" { + pairs[args[i]] = "true" + i-- + continue + } + pairs[args[i]] = args[i+1] + } + return pairs } - t.Run("NoExtras", func(t *testing.T) { - vmConfig := NewOpProgramServerExecutor() - - args, err := vmConfig.OracleCommand(cfg, dir, inputs) + oracleCommand := func(t *testing.T, lvl slog.Level, configModifier func(c *Config)) map[string]string { + cfg := Config{ + L1: "http://localhost:8888", + L1Beacon: "http://localhost:9000", + L2: "http://localhost:9999", + Server: "./bin/mockserver", + } + inputs := utils.LocalGameInputs{ + L1Head: common.Hash{0x11}, + L2Head: common.Hash{0x22}, + L2OutputRoot: common.Hash{0x33}, + L2Claim: common.Hash{0x44}, + L2BlockNumber: big.NewInt(3333), + } + configModifier(&cfg) + executor := NewOpProgramServerExecutor(testlog.Logger(t, lvl)) + + args, err := executor.OracleCommand(cfg, dir, inputs) require.NoError(t, err) + pairs := toPairs(args) + // Validate standard options + require.Equal(t, "--server", pairs[cfg.Server]) + require.Equal(t, cfg.L1, pairs["--l1"]) + require.Equal(t, cfg.L1Beacon, pairs["--l1.beacon"]) + require.Equal(t, cfg.L2, pairs["--l2"]) + require.Equal(t, dir, pairs["--datadir"]) + require.Equal(t, inputs.L1Head.Hex(), pairs["--l1.head"]) + require.Equal(t, inputs.L2Head.Hex(), pairs["--l2.head"]) + require.Equal(t, inputs.L2OutputRoot.Hex(), pairs["--l2.outputroot"]) + require.Equal(t, inputs.L2Claim.Hex(), pairs["--l2.claim"]) + require.Equal(t, inputs.L2BlockNumber.String(), pairs["--l2.blocknumber"]) + return pairs + } - validateStandard(t, args) + t.Run("NoExtras", func(t *testing.T) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config) {}) + require.NotContains(t, pairs, "--network") + require.NotContains(t, pairs, "--rollup.config") + require.NotContains(t, pairs, "--l2.genesis") }) t.Run("WithNetwork", func(t *testing.T) { - cfg.Network = "op-test" - vmConfig := NewOpProgramServerExecutor() - - args, err := vmConfig.OracleCommand(cfg, dir, inputs) - require.NoError(t, err) + pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + c.Network = "op-test" + }) + require.Equal(t, "op-test", pairs["--network"]) + }) - validateStandard(t, args) - require.True(t, slices.Contains(args, "--network")) + t.Run("WithL2ChainID", func(t *testing.T) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + c.L2Custom = true + }) + require.Equal(t, "true", pairs["--l2.custom"]) }) t.Run("WithRollupConfigPath", func(t *testing.T) { - cfg.RollupConfigPath = "rollup.config" - vmConfig := NewOpProgramServerExecutor() - - args, err := vmConfig.OracleCommand(cfg, dir, inputs) - require.NoError(t, err) - - validateStandard(t, args) - require.True(t, slices.Contains(args, "--rollup.config")) + pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + c.RollupConfigPath = "rollup.config.json" + }) + require.Equal(t, "rollup.config.json", pairs["--rollup.config"]) }) t.Run("WithL2GenesisPath", func(t *testing.T) { - cfg.L2GenesisPath = "l2.genesis" - vmConfig := NewOpProgramServerExecutor() - - args, err := vmConfig.OracleCommand(cfg, dir, inputs) - require.NoError(t, err) - - validateStandard(t, args) - require.True(t, slices.Contains(args, "--l2.genesis")) + pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + c.L2GenesisPath = "genesis.json" + }) + require.Equal(t, "genesis.json", pairs["--l2.genesis"]) }) t.Run("WithAllExtras", func(t *testing.T) { - cfg.Network = "op-test" - cfg.RollupConfigPath = "rollup.config" - cfg.L2GenesisPath = "l2.genesis" - vmConfig := NewOpProgramServerExecutor() - - args, err := vmConfig.OracleCommand(cfg, dir, inputs) - require.NoError(t, err) - - validateStandard(t, args) - require.True(t, slices.Contains(args, "--network")) - require.True(t, slices.Contains(args, "--rollup.config")) - require.True(t, slices.Contains(args, "--l2.genesis")) + pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + c.Network = "op-test" + c.RollupConfigPath = "rollup.config.json" + c.L2GenesisPath = "genesis.json" + }) + require.Equal(t, "op-test", pairs["--network"]) + require.Equal(t, "rollup.config.json", pairs["--rollup.config"]) + require.Equal(t, "genesis.json", pairs["--l2.genesis"]) }) + + logTests := []struct { + level slog.Level + arg string + }{ + {log.LevelTrace, "TRACE"}, + {log.LevelDebug, "DEBUG"}, + {log.LevelInfo, "INFO"}, + {log.LevelWarn, "WARN"}, + {log.LevelError, "ERROR"}, + {log.LevelCrit, "CRIT"}, + } + for _, logTest := range logTests { + logTest := logTest + t.Run(fmt.Sprintf("LogLevel-%v", logTest.arg), func(t *testing.T) { + pairs := oracleCommand(t, logTest.level, func(c *Config) {}) + require.Equal(t, pairs["--log.level"], logTest.arg) + }) + } } diff --git a/op-challenger/justfile b/op-challenger/justfile new file mode 100644 index 0000000000000..ccb2b5fa7e916 --- /dev/null +++ b/op-challenger/justfile @@ -0,0 +1,25 @@ +import '../just/go.just' + +# Build ldflags string +_VERSION_META_STR := if VERSION_META != "" { "+" + VERSION_META } else { "" } +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X github.com/ethereum-optimism/optimism/op-challenger/version.Version=" + VERSION + " " + \ + "-X github.com/ethereum-optimism/optimism/op-challenger/version.Meta=" + _VERSION_META_STR + " " + \ + "") + "'" + +BINARY := "./bin/op-challenger" + +# Build op-challenger binary +op-challenger: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Run fuzzing tests +fuzz: (go_fuzz "FuzzKeccak" "10s" "./game/keccak/matrix") + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") diff --git a/op-challenger/runner/factory.go b/op-challenger/runner/factory.go index 6aab9642778e9..8a9929bd0a40a 100644 --- a/op-challenger/runner/factory.go +++ b/op-challenger/runner/factory.go @@ -30,7 +30,7 @@ func createTraceProvider( ) (types.TraceProvider, error) { switch traceType { case types.TraceTypeCannon: - serverExecutor := vm.NewOpProgramServerExecutor() + serverExecutor := vm.NewOpProgramServerExecutor(logger) stateConverter := cannon.NewStateConverter(cfg.Cannon) prestate, err := getPrestate(ctx, prestateHash, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState, dir, stateConverter) if err != nil { @@ -39,7 +39,7 @@ func createTraceProvider( prestateProvider := vm.NewPrestateProvider(prestate, stateConverter) return cannon.NewTraceProvider(logger, m, cfg.Cannon, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil case types.TraceTypeAsterisc: - serverExecutor := vm.NewOpProgramServerExecutor() + serverExecutor := vm.NewOpProgramServerExecutor(logger) stateConverter := asterisc.NewStateConverter(cfg.Asterisc) prestate, err := getPrestate(ctx, prestateHash, cfg.AsteriscAbsolutePreStateBaseURL, cfg.AsteriscAbsolutePreState, dir, stateConverter) if err != nil { @@ -60,28 +60,6 @@ func createTraceProvider( return nil, errors.New("invalid trace type") } -func createMTTraceProvider( - ctx context.Context, - logger log.Logger, - m vm.Metricer, - vmConfig vm.Config, - prestateHash common.Hash, - absolutePrestateBaseURL *url.URL, - localInputs utils.LocalGameInputs, - dir string, -) (types.TraceProvider, error) { - executor := vm.NewOpProgramServerExecutor() - stateConverter := cannon.NewStateConverter(vmConfig) - - prestateSource := prestates.NewMultiPrestateProvider(absolutePrestateBaseURL, filepath.Join(dir, "prestates"), stateConverter) - prestatePath, err := prestateSource.PrestatePath(ctx, prestateHash) - if err != nil { - return nil, fmt.Errorf("failed to get prestate %v: %w", prestateHash, err) - } - prestateProvider := vm.NewPrestateProvider(prestatePath, stateConverter) - return cannon.NewTraceProvider(logger, m, vmConfig, executor, prestateProvider, prestatePath, localInputs, dir, 42), nil -} - func getPrestate(ctx context.Context, prestateHash common.Hash, prestateBaseUrl *url.URL, prestatePath string, dataDir string, stateConverter vm.StateConverter) (string, error) { prestateSource := prestates.NewPrestateSource( prestateBaseUrl, diff --git a/op-challenger/runner/runner.go b/op-challenger/runner/runner.go index 8e12d6ae05530..312981430413b 100644 --- a/op-challenger/runner/runner.go +++ b/op-challenger/runner/runner.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" "math/big" - "net/url" "os" "path/filepath" + "regexp" "sync" "sync/atomic" "time" @@ -29,8 +29,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -const mtCannonType = "mt-cannon" - var ( ErrUnexpectedStatusCode = errors.New("unexpected status code") ) @@ -45,12 +43,17 @@ type Metricer interface { RecordSuccess(vmType string) } +type RunConfig struct { + TraceType types.TraceType + Name string + Prestate common.Hash +} + type Runner struct { - log log.Logger - cfg *config.Config - addMTCannonPrestate common.Hash - addMTCannonPrestateURL *url.URL - m Metricer + log log.Logger + cfg *config.Config + runConfigs []RunConfig + m Metricer running atomic.Bool ctx context.Context @@ -59,13 +62,12 @@ type Runner struct { metricsSrv *httputil.HTTPServer } -func NewRunner(logger log.Logger, cfg *config.Config, mtCannonPrestate common.Hash, mtCannonPrestateURL *url.URL) *Runner { +func NewRunner(logger log.Logger, cfg *config.Config, runConfigs []RunConfig) *Runner { return &Runner{ - log: logger, - cfg: cfg, - addMTCannonPrestate: mtCannonPrestate, - addMTCannonPrestateURL: mtCannonPrestateURL, - m: NewMetrics(), + log: logger, + cfg: cfg, + runConfigs: runConfigs, + m: NewMetrics(), } } @@ -91,21 +93,21 @@ func (r *Runner) Start(ctx context.Context) error { } caller := batching.NewMultiCaller(l1Client, batching.DefaultBatchSize) - for _, traceType := range r.cfg.TraceTypes { + for _, runConfig := range r.runConfigs { r.wg.Add(1) - go r.loop(ctx, traceType, rollupClient, caller) + go r.loop(ctx, runConfig, rollupClient, caller) } - r.log.Info("Runners started") + r.log.Info("Runners started", "num", len(r.runConfigs)) return nil } -func (r *Runner) loop(ctx context.Context, traceType types.TraceType, client *sources.RollupClient, caller *batching.MultiCaller) { +func (r *Runner) loop(ctx context.Context, runConfig RunConfig, client *sources.RollupClient, caller *batching.MultiCaller) { defer r.wg.Done() t := time.NewTicker(1 * time.Minute) defer t.Stop() for { - r.runAndRecordOnce(ctx, traceType, client, caller) + r.runAndRecordOnce(ctx, runConfig, client, caller) select { case <-t.C: case <-ctx.Done(): @@ -114,80 +116,50 @@ func (r *Runner) loop(ctx context.Context, traceType types.TraceType, client *so } } -func (r *Runner) runAndRecordOnce(ctx context.Context, traceType types.TraceType, client *sources.RollupClient, caller *batching.MultiCaller) { +func (r *Runner) runAndRecordOnce(ctx context.Context, runConfig RunConfig, client *sources.RollupClient, caller *batching.MultiCaller) { recordError := func(err error, traceType string, m Metricer, log log.Logger) { if errors.Is(err, ErrUnexpectedStatusCode) { - log.Error("Incorrect status code", "type", traceType, "err", err) + log.Error("Incorrect status code", "type", runConfig.Name, "err", err) m.RecordInvalid(traceType) } else if err != nil { - log.Error("Failed to run", "type", traceType, "err", err) + log.Error("Failed to run", "type", runConfig.Name, "err", err) m.RecordFailure(traceType) } else { - log.Info("Successfully verified output root", "type", traceType) + log.Info("Successfully verified output root", "type", runConfig.Name) m.RecordSuccess(traceType) } } - prestateHash, err := r.getPrestateHash(ctx, traceType, caller) - if err != nil { - recordError(err, traceType.String(), r.m, r.log) - return + prestateHash := runConfig.Prestate + if prestateHash == (common.Hash{}) { + hash, err := r.getPrestateHash(ctx, runConfig.TraceType, caller) + if err != nil { + recordError(err, runConfig.Name, r.m, r.log) + return + } + prestateHash = hash } localInputs, err := r.createGameInputs(ctx, client) if err != nil { - recordError(err, traceType.String(), r.m, r.log) + recordError(err, runConfig.Name, r.m, r.log) return } inputsLogger := r.log.New("l1", localInputs.L1Head, "l2", localInputs.L2Head, "l2Block", localInputs.L2BlockNumber, "claim", localInputs.L2Claim) - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - dir, err := r.prepDatadir(traceType.String()) - if err != nil { - recordError(err, traceType.String(), r.m, r.log) - return - } - err = r.runOnce(ctx, inputsLogger.With("type", traceType), traceType, prestateHash, localInputs, dir) - recordError(err, traceType.String(), r.m, r.log) - }() - - if traceType == types.TraceTypeCannon && r.addMTCannonPrestate != (common.Hash{}) && r.addMTCannonPrestateURL != nil { - wg.Add(1) - go func() { - defer wg.Done() - dir, err := r.prepDatadir(mtCannonType) - if err != nil { - recordError(err, mtCannonType, r.m, r.log) - return - } - logger := inputsLogger.With("type", mtCannonType) - err = r.runMTOnce(ctx, logger, localInputs, dir) - recordError(err, mtCannonType, r.m, r.log.With(mtCannonType, true)) - }() - } - wg.Wait() -} - -func (r *Runner) runOnce(ctx context.Context, logger log.Logger, traceType types.TraceType, prestateHash common.Hash, localInputs utils.LocalGameInputs, dir string) error { - provider, err := createTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, traceType.String()), r.cfg, prestateHash, traceType, localInputs, dir) - if err != nil { - return fmt.Errorf("failed to create trace provider: %w", err) - } - hash, err := provider.Get(ctx, types.RootPosition) + // Sanitize the directory name. + safeName := regexp.MustCompile("[^a-zA-Z0-9_-]").ReplaceAllString(runConfig.Name, "") + dir, err := r.prepDatadir(safeName) if err != nil { - return fmt.Errorf("failed to execute trace provider: %w", err) - } - if hash[0] != mipsevm.VMStatusValid { - return fmt.Errorf("%w: %v", ErrUnexpectedStatusCode, hash) + recordError(err, runConfig.Name, r.m, r.log) + return } - return nil + err = r.runOnce(ctx, inputsLogger.With("type", runConfig.Name), runConfig.Name, runConfig.TraceType, prestateHash, localInputs, dir) + recordError(err, runConfig.Name, r.m, r.log) } -func (r *Runner) runMTOnce(ctx context.Context, logger log.Logger, localInputs utils.LocalGameInputs, dir string) error { - provider, err := createMTTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, mtCannonType), r.cfg.Cannon, r.addMTCannonPrestate, r.addMTCannonPrestateURL, localInputs, dir) +func (r *Runner) runOnce(ctx context.Context, logger log.Logger, name string, traceType types.TraceType, prestateHash common.Hash, localInputs utils.LocalGameInputs, dir string) error { + provider, err := createTraceProvider(ctx, logger, metrics.NewVmMetrics(r.m, name), r.cfg, prestateHash, traceType, localInputs, dir) if err != nil { return fmt.Errorf("failed to create trace provider: %w", err) } @@ -201,8 +173,8 @@ func (r *Runner) runMTOnce(ctx context.Context, logger log.Logger, localInputs u return nil } -func (r *Runner) prepDatadir(traceType string) (string, error) { - dir := filepath.Join(r.cfg.Datadir, traceType) +func (r *Runner) prepDatadir(name string) (string, error) { + dir := filepath.Join(r.cfg.Datadir, name) if err := os.RemoveAll(dir); err != nil { return "", fmt.Errorf("failed to remove old dir: %w", err) } @@ -227,6 +199,9 @@ func (r *Runner) createGameInputs(ctx context.Context, client *sources.RollupCli // This only matters if op-node is behind and hasn't processed all finalized L1 blocks yet. l1Head = status.CurrentL1 } + if l1Head.Number == 0 { + return utils.LocalGameInputs{}, errors.New("l1 head is 0") + } blockNumber, err := r.findL2BlockNumberToDispute(ctx, client, l1Head.Number, status.FinalizedL2.Number) if err != nil { return utils.LocalGameInputs{}, fmt.Errorf("failed to find l2 block number to dispute: %w", err) diff --git a/op-challenger/sender/sender_test.go b/op-challenger/sender/sender_test.go index 5169ae67192ad..1d4e17047b74a 100644 --- a/op-challenger/sender/sender_test.go +++ b/op-challenger/sender/sender_test.go @@ -129,8 +129,14 @@ func (s *stubTxMgr) Send(ctx context.Context, candidate txmgr.TxCandidate) (*typ return <-ch, nil } +// SendAsync simply wraps Send to make it non blocking. It does not guarantee transaction nonce ordering, +// unlike the production txMgr. func (s *stubTxMgr) SendAsync(ctx context.Context, candidate txmgr.TxCandidate, ch chan txmgr.SendResponse) { - panic("unimplemented") + go func() { + receipt, err := s.Send(ctx, candidate) + resp := txmgr.SendResponse{Receipt: receipt, Err: err} + ch <- resp + }() } func (s *stubTxMgr) recordTx(candidate txmgr.TxCandidate) chan *types.Receipt { diff --git a/op-conductor/Makefile b/op-conductor/Makefile index 6360df3da0c84..c76af2d563c6d 100644 --- a/op-conductor/Makefile +++ b/op-conductor/Makefile @@ -1,26 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -VERSION ?= v0.0.0 +DEPRECATED_TARGETS := op-conductor clean test generate-mocks -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.Version=$(VERSION) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -op-conductor: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-conductor ./cmd - -clean: - rm bin/op-conductor - -test: - go test -v ./... - -generate-mocks: - go generate ./... - -.PHONY: \ - op-conductor \ - clean \ - test \ - generate-mocks +include ../just/deprecated.mk diff --git a/op-conductor/README.md b/op-conductor/README.md index 8cb3afcfe34a3..c436d0248ace7 100644 --- a/op-conductor/README.md +++ b/op-conductor/README.md @@ -1,3 +1,4 @@ + # op-conductor op-conductor is an auxiliary service designed to enhance the reliability and availability of a sequencer in @@ -13,9 +14,17 @@ The design will provide below guarantees: 2. No unsafe head stall during network partition 3. 100% uptime with no more than 1 node failure (for a standard 3 node setup) +For configuration and runbook, please refer to [RUNBOOK.md](./RUNBOOK.md) + ## Design -![op-conductor architecture](./assets/op-conductor.svg) +### Architecture + +Typically you can setup a 3 nodes sequencer cluster, each one with op-conductor running alongside the sequencer in different regions / AZs. +Below diagram showcases how conductor interacts with relevant op-stack components. + +![op-conductor setup](./assets/setup.svg) + On a high level, op-conductor serves the following functions: 1. serves as a (raft) consensus layer participant to determine @@ -27,6 +36,8 @@ On a high level, op-conductor serves the following functions: 3. monitor sequencer (op-node) health 4. control loop => control sequencer (op-node) status (start / stop) based on different scenarios +![op-conductor architecture](./assets/op-conductor.svg) + ### Conductor State Transition ![conductor state transition](./assets/op-conductor-state-transition.svg) @@ -35,4 +46,54 @@ Helpful tips: To better understand the graph, focus on one node at a time, understand what can be transitioned to this current state and how it can transition to other states. This way you could understand how we handle the state transitions. -This is initial version of README, more details will be added later. +### RPC design + +conductor provides rpc APIs for ease of cluster management, see [api](./op-conductor/rpc/api.go) for rpc definitions and how they can be used. + +### Failure Scenario walkthrough + +Below is a high level summary of how each failure scenario is handled: + +| Failure Scenario | Category | Scenario and Solution | Notes | +| ---------------- | -------- | --------------------- | ----- | +| 1 sequencer down, stopped producing blocks | sequencer failure | Conductor will detect sequencer failure and start to transfer leadership to another node, which will start sequencing instead | Cannot tolerate 2 node failures, at that time, would need to manually start sequencing on the remaining sequencer | +| 1 sequencer temporarily down, we transferred leadership to another sequencer, but it came back after leadership transfer succeeded and still be in sequencing mode | Sequencer failure | We stop this by:
1. commit latest unsafe block to conductor, if node is not leader, commit fails and the block won't be gossiped out, this prevents any p2p blocks going out to the network.
2. for control loop health update handling logic, stop sequencer when it’s not leader but healthy.
| | +| 2 or more sequencer down (regardless of if active sequencer is healthy) | sequencer failure | Scenario #1: active sequencer and 1 follower are down

Solution:
At this point, the Conductor will notice the sequencer not being healthy and start to transfer leadership to another node. To avoid leadership transfer between two faulty nodes, we will implement a round robin mechanism to choose a leader to avoid the cycle. | | +| 2 or more sequencer down (regardless of if active sequencer is healthy) | sequencer failure | Scenario #2: 2 standby are down

Solution:
Cluster will still be healthy, active sequencer is still working, and raft consensus is healthy as well, so no leadership transfer will happen (standby sequencer is not leader and will not be able to start leadership transfer process, and we will have logic to rule this out).

So the solution here is adding monitors for the health of every node and fixing it by any means needed. | | +| one Conductor failed / stopped working | Conductor failure | At this point:
1. the raft cluster will elect a leader on the remaining healthy nodes and start sequencer
2. however, old sequencer isn’t shut down yet (since Conductor is down and unable to perform the action)

Solution
1. Before enabling sequencing on the new sequencer, make sure no other sequencer is running by sending admin_stopSequencer / stopBatcher commands to all participating nodes | | +| 2 or more Conductor failed | Conductor failure | At this point, the cluster will work in two scenarios depending on the failure scenario:
1. if service on 2 standby sequencer failed, active sequencer service will step down from leader and stop sequencing
2. if service failed on 1 active and 1 standby sequencer, chain will still work as active sequencer will not be turned off due to Conductor failure

Solution
1. This should be an extremely rare situation and we can manually start sequencing on the active sequencer
2. network is still healthy at this time, we just need to be notified and fix the Conductor, pause control loop | | +| Infra failure
* sequencer host disappeared
* region failure
* etc | Infra failure | Conductor alongside with active sequencer will no long exist, and with raft protocol, a new leader will be elected within the next 1s (300ms heartbeat) and start producing blocks | Cannot tolerate 2 node failures, at that time, would need to manually start sequencing on the remaining sequencer | +| There is a network partition that leads to some nodes not able to talk to others for leader election / lease renewal | Network partition | This should be handled automatically by raft consensus protocol, it will guarantee that there will be at most one leader at any point in time. | In extreme situations where none of the raft nodes are able to talk to each other (therefore form a quorum), no leader will exist and we will need to manually start a sequencer in a healthy node. | +| split brain during deployment where there will be 4 sequencers running (1 new sequencer deploy) | Sequencer deployment / upgrades | During deployment, we will start a 4th sequencer and try to replace one of the 3 sequencers in the raft consensus protocol, how to switch atomically is key to prevent split brain situations.

Solution
1. Add new sequencer as nonvoter first
2. When new sequencer catches up to tip
a. Remove old sequencer from server group
b. Promote new sequencer to be voter | | +| 2 or more nodes (either Conductor or sequencer) are down | Disaster recovery | During this situation, there should be no healthy leader in the raft cluster and no sequencer actively producing blocks.

Solution
1. manual intervention with tooling built to stop using Conductor to manage sequencer
2. force start sequencer on one of the working sequencer | | + +### Leadership Transfer Process + +During leadership transfer, we need to determine: + +1. Which block hash we want to start sequencing on the sequencer candidate (i.e. what is the canonical chain head?) +2. Which candidate do we want to transfer leadership to? (ideally the one with the latest block information) + +#### Which block hash we want to start sequencing on the sequencer candidate (i.e. what is the canonical chain head?) + +To decide the canonical chain head, we will implement a FSM inside raft consensus protocol that stores the head unsafe block payloads. + +However, we still need to deal with some edge cases which is illustrated in the diagram below: + +![leader-transfer](./assets/leader-transfer.svg) + +1. Case #1 (happy case) should be majority of the case, we will experience a smooth leadership transfer with no issue +2. Case #2 (failure case) if latest block X failed to be gossiped out, but was already committed in the consensus layer, we need to +statically pair conductor with sequencer in p2p (without exposing it to the broader network), and get the latest unsafe block and gossip it to sequencer +this way, we could guarantee that there will always be a latest block being synced to the newly elected leader (either through normal p2p or through conductor) +3. Case #3 (failure case), if the conductor is down and we fail to commit the latest hash to the consensus layer, starting the new sequencer elsewhere would be safe. + +#### Which candidate do we want to transfer leadership to? (ideally the one with the latest block information) + +There are 2 situations we need to consider. + +1. Leadership transfer triggered by raft consensus protocol (network partition, etc) + 1. In this case, a new leader will be elected regardless of its sync status, it could be behind for a few blocks + 2. The solution is to simply wait until the elected leader catches up to tip (same as the FSM tip) +2. Leadership transfer triggered by us (Conductor detected unhealthy sequencer) + 1. In this case, we have the choice to determine which node to transfer leadership to, we can simply query the latest block from candidates within the network and transfer directly to the one with the most up to date blocks. diff --git a/op-conductor/RUNBOOK.md b/op-conductor/RUNBOOK.md new file mode 100644 index 0000000000000..00b8757a338eb --- /dev/null +++ b/op-conductor/RUNBOOK.md @@ -0,0 +1,78 @@ +## op-conductor runbook + +### conductor configurations + +In order to setup op-conductor, you need to configure the following env vars for both op-conductor and op-node service: + +#### op-node + +```env +OP_NODE_CONDUCTOR_ENABLED=true +OP_NODE_CONDUCTOR_RPC= # for example http://conductor:8545 +``` + +#### op-conductor + +```env +# prefix for the server id, used to identify the server in the raft cluster +RAFT_SERVER_ID_PREFIX= # for example, sequencer-1, sequencer-2, etc +OP_CONDUCTOR_RAFT_STORAGE_DIR= +OP_CONDUCTOR_RPC_ADDR= # for example, 0.0.0.0 +OP_CONDUCTOR_RPC_PORT= # for example, 8545 +OP_CONDUCTOR_METRICS_ENABLED=true/false +OP_CONDUCTOR_METRICS_ADDR= # for example 0.0.0.0 +OP_CONDUCTOR_METRICS_PORT= # for example 7300 +OP_CONDUCTOR_CONSENSUS_PORT= # for example 50050 +OP_CONDUCTOR_PAUSED=true # set to true to start conductor in paused state +OP_CONDUCTOR_NODE_RPC= # for example, http://op-node:8545 +OP_CONDUCTOR_EXECUTION_RPC= # for example, http://op-geth:8545 +OP_CONDUCTOR_NETWORK= # for example, base-mainnet, op-mainnet, etc, should be same as OP_NODE_NETWORK +OP_CONDUCTOR_HEALTHCHECK_INTERVAL= # in seconds +OP_CONDUCTOR_HEALTHCHECK_UNSAFE_INTERVAL= # Interval allowed between unsafe head and now measured in seconds +OP_CONDUCTOR_HEALTHCHECK_MIN_PEER_COUNT= # minimum number of peers required to be considered healthy +OP_CONDUCTOR_RAFT_BOOTSTRAP=true/false # set to true if you want to bootstrap the raft cluster +``` + +### How to bootstrap a sequencer cluster from scratch + +In normal situations, you probably have a running sequencer already and you want to turn it into a HA cluster. What you need to do in this situation is to: + +1. start a completely new sequencer with above mentioned configurations and + 1. `OP_CONDUCTOR_RAFT_BOOTSTRAP=true` set on op-conductor + 2. `OP_CONDUCTOR_PAUSED=true` set on op-conductor + 3. `OP_NODE_SEQUENCER_ENABLED=true` set on op-node +2. wait for the new sequencer to start and get synced up with the rest of the nodes +3. once the new sequencer is synced up, manually or use automation to stop sequencing on the old sequencer and start sequencing on the new sequencer +4. resume the conductor on the new sequencer by calling `conductor_resume` json rpc method on op-conductor +5. set `OP_CONDUCTOR_RAFT_BOOTSTRAP=false` on the sequencer so that it doesn't attempt to bootstrap the cluster during redeploy + +Now you have a single HA sequencer which treats itself as the cluster leader! Next steps would be to add more sequencers to the cluster depending on your needs. For example, we want a 3-node cluster, you can follow the same process to add 2 more sequencers. + +1. start a new sequencer with + 1. `OP_CONDUCTOR_RAFT_BOOTSTRAP=false` set on op-conductor + 2. `OP_CONDUCTOR_PAUSED=true` set on op-conductor +2. wait for the new sequencer to start and get synced up with the rest of the nodes +3. once the new sequencer is synced up, manually or use automation to add it to the cluster by calling `conductor_addServerAsVoter` json rpc method on the leader sequencer +4. call `conductor_clusterMembership` json rpc method on the leader sequencer to get the updated cluster membership +5. resume the conductor on the new sequencer by calling `conductor_resume` json rpc method on op-conductor + +Once finished, you should have a 3-node HA sequencer cluster! + +### Redeploy a HA sequencer + +For every redeploy, depending on your underlying infrastructure, you need to make sure to: + +1. `OP_CONDUCTOR_PAUSED=true` set on op-conductor so that conductor doesn't attempt to control sequencer while it's still syncing / redeploying +2. make sure sequencer is caught up with the rest of the nodes (this step isn't strictly necessary as conductor could handle this, but from a HA perspective, it does not make sense to have a sequencer that is lagging behind to join the cluster to potentially become the leader) +3. resume conductor after it's caught up with the rest of the nodes so that conductor can start managing the sequencer + +### Disaster recovery + +Whenever there are a disaster situation that you see no route to have 2 healthy conductor in the cluster communicating with each other, you need to manually intervene to resume sequencing. The steps are as follows: + +1. call `conductor_pause` json rpc method on the all conductors so that they don't attempt to start / stop sequencer +2. choose a sequencer that can be used to resume sequencing +3. call `conductor_overrideLeader` json rpc method on the conductor to force it to treat itself as the leader +4. If no conductor is functioning, call `admin_overrideLeader` json rpc method on the op-node to force it to treat itself as the leader +5. manually start sequencing on the chosen sequencer +6. Go back to bootstrap step to re-bootstrap the cluster. diff --git a/op-conductor/assets/leader-transfer.svg b/op-conductor/assets/leader-transfer.svg new file mode 100644 index 0000000000000..132bdd141a94e --- /dev/null +++ b/op-conductor/assets/leader-transfer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/op-conductor/assets/setup.svg b/op-conductor/assets/setup.svg new file mode 100644 index 0000000000000..62048d79fadfc --- /dev/null +++ b/op-conductor/assets/setup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/op-conductor/conductor/config.go b/op-conductor/conductor/config.go index ca18de7d1a18f..98e3ad83440ef 100644 --- a/op-conductor/conductor/config.go +++ b/op-conductor/conductor/config.go @@ -19,12 +19,19 @@ import ( ) type Config struct { - // ConsensusAddr is the address to listen for consensus connections. + // ConsensusAddr is the address, excluding port, to listen on for consensus connections. + // E.g. 0.0.0.0 to bind to the external-facing network interface. ConsensusAddr string - // ConsensusPort is the port to listen for consensus connections. + // ConsensusPort is the port to listen on for consensus connections. + // If 0, the server binds to a port selected by the system. ConsensusPort int + // ConsensusAdvertisedAddr is the network address, including port, to advertise to other peers. + // This is optional: if empty, the address that the server network transport binds to is used instead. + // E.g. local tests may use temporary addresses, rather than preset known addresses. + ConsensusAdvertisedAddr string + // RaftServerID is the unique ID for this server used by raft consensus. RaftServerID string @@ -117,8 +124,11 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*Config, error) { } return &Config{ - ConsensusAddr: ctx.String(flags.ConsensusAddr.Name), - ConsensusPort: ctx.Int(flags.ConsensusPort.Name), + ConsensusAddr: ctx.String(flags.ConsensusAddr.Name), + ConsensusPort: ctx.Int(flags.ConsensusPort.Name), + // The consensus server will advertise the address it binds to if this is empty/unspecified. + ConsensusAdvertisedAddr: ctx.String(flags.AdvertisedFullAddr.Name), + RaftBootstrap: ctx.Bool(flags.RaftBootstrap.Name), RaftServerID: ctx.String(flags.RaftServerID.Name), RaftStorageDir: ctx.String(flags.RaftStorageDir.Name), diff --git a/op-conductor/conductor/service.go b/op-conductor/conductor/service.go index f93314f5f70b2..89948b614f06f 100644 --- a/op-conductor/conductor/service.go +++ b/op-conductor/conductor/service.go @@ -78,9 +78,10 @@ func NewOpConductor( oc.loopActionFn = oc.loopAction // explicitly set all atomic.Bool values - oc.leader.Store(false) // upon start, it should not be the leader unless specified otherwise by raft bootstrap, in that case, it'll receive a leadership update from consensus. - oc.healthy.Store(true) // default to healthy unless reported otherwise by health monitor. - oc.seqActive.Store(false) // explicitly set to false by default, the real value will be reported after sequencer control initialization. + oc.leader.Store(false) // upon start, it should not be the leader unless specified otherwise by raft bootstrap, in that case, it'll receive a leadership update from consensus. + oc.leaderOverride.Store(false) // default to no override. + oc.healthy.Store(true) // default to healthy unless reported otherwise by health monitor. + oc.seqActive.Store(false) // explicitly set to false by default, the real value will be reported after sequencer control initialization. oc.paused.Store(cfg.Paused) oc.stopped.Store(false) @@ -168,10 +169,12 @@ func (c *OpConductor) initConsensus(ctx context.Context) error { return nil } - serverAddr := fmt.Sprintf("%s:%d", c.cfg.ConsensusAddr, c.cfg.ConsensusPort) raftConsensusConfig := &consensus.RaftConsensusConfig{ - ServerID: c.cfg.RaftServerID, - ServerAddr: serverAddr, + ServerID: c.cfg.RaftServerID, + // AdvertisedAddr may be empty: the server will then default to what it binds to. + AdvertisedAddr: raft.ServerAddress(c.cfg.ConsensusAdvertisedAddr), + ListenAddr: c.cfg.ConsensusAddr, + ListenPort: c.cfg.ConsensusPort, StorageDir: c.cfg.RaftStorageDir, Bootstrap: c.cfg.RaftBootstrap, RollupCfg: &c.cfg.RollupCfg, @@ -246,6 +249,11 @@ func (oc *OpConductor) initRPCServer(ctx context.Context) error { Namespace: conductorrpc.ExecutionRPCNamespace, Service: executionProxy, }) + execMinerProxy := conductorrpc.NewExecutionMinerProxyBackend(oc.log, oc, execClient) + server.AddAPI(rpc.API{ + Namespace: conductorrpc.ExecutionMinerRPCNamespace, + Service: execMinerProxy, + }) nodeClient, err := dial.DialRollupClientWithTimeout(ctx, 1*time.Minute, oc.log, oc.cfg.NodeRPC) if err != nil { @@ -287,11 +295,12 @@ type OpConductor struct { cons consensus.Consensus hmon health.HealthMonitor - leader atomic.Bool - seqActive atomic.Bool - healthy atomic.Bool - hcerr error // error from health check - prevState *state + leader atomic.Bool + leaderOverride atomic.Bool + seqActive atomic.Bool + healthy atomic.Bool + hcerr error // error from health check + prevState *state healthUpdateCh <-chan error leaderUpdateCh <-chan bool @@ -372,7 +381,9 @@ func (oc *OpConductor) Start(ctx context.Context) error { oc.log.Info("OpConductor started") // queue an action in case sequencer is not in the desired state. oc.prevState = NewState(oc.leader.Load(), oc.healthy.Load(), oc.seqActive.Load()) - oc.queueAction() + // Immediately queue an action. This is made blocking to ensure that start is not + // considered complete until the first action is executed. + oc.actionCh <- struct{}{} return nil } @@ -465,6 +476,12 @@ func (oc *OpConductor) Paused() bool { return oc.paused.Load() } +// ConsensusEndpoint returns the raft consensus server address to connect to. +func (oc *OpConductor) ConsensusEndpoint() string { + return oc.cons.Addr() +} + +// HTTPEndpoint returns the HTTP RPC endpoint func (oc *OpConductor) HTTPEndpoint() string { if oc.rpcServer == nil { return "" @@ -472,13 +489,29 @@ func (oc *OpConductor) HTTPEndpoint() string { return fmt.Sprintf("http://%s", oc.rpcServer.Endpoint()) } +func (oc *OpConductor) OverrideLeader(override bool) { + oc.leaderOverride.Store(override) +} + +func (oc *OpConductor) LeaderOverridden() bool { + return oc.leaderOverride.Load() +} + // Leader returns true if OpConductor is the leader. -func (oc *OpConductor) Leader(_ context.Context) bool { - return oc.cons.Leader() +func (oc *OpConductor) Leader(ctx context.Context) bool { + return oc.LeaderOverridden() || oc.cons.Leader() } // LeaderWithID returns the current leader's server ID and address. -func (oc *OpConductor) LeaderWithID(_ context.Context) *consensus.ServerInfo { +func (oc *OpConductor) LeaderWithID(ctx context.Context) *consensus.ServerInfo { + if oc.LeaderOverridden() { + return &consensus.ServerInfo{ + ID: "N/A (Leader overridden)", + Addr: "N/A", + Suffrage: 0, + } + } + return oc.cons.LeaderWithID() } @@ -590,7 +623,8 @@ func (oc *OpConductor) handleHealthUpdate(hcerr error) { oc.queueAction() } - if oc.healthy.Swap(healthy) != healthy { + if old := oc.healthy.Swap(healthy); old != healthy { + oc.log.Info("Health state changed", "old", old, "new", healthy) // queue an action if health status changed. oc.queueAction() } diff --git a/op-conductor/conductor/service_test.go b/op-conductor/conductor/service_test.go index 49a05e9027639..87df417a4683e 100644 --- a/op-conductor/conductor/service_test.go +++ b/op-conductor/conductor/service_test.go @@ -30,7 +30,7 @@ func mockConfig(t *testing.T) Config { now := uint64(time.Now().Unix()) return Config{ ConsensusAddr: "127.0.0.1", - ConsensusPort: 50050, + ConsensusPort: 0, RaftServerID: "SequencerA", RaftStorageDir: "/tmp/raft", RaftBootstrap: false, diff --git a/op-conductor/consensus/iface.go b/op-conductor/consensus/iface.go index 69b9506c50b26..2de955c1201ec 100644 --- a/op-conductor/consensus/iface.go +++ b/op-conductor/consensus/iface.go @@ -42,6 +42,9 @@ type ServerInfo struct { // //go:generate mockery --name Consensus --output mocks/ --with-expecter=true type Consensus interface { + // Addr returns the address of this consensus server. + // Internally the server may override what is advertised, or fall back to the address it listens to. + Addr() string // AddVoter adds a voting member into the cluster, voter is eligible to become leader. // If version is non-zero, this will only be applied if the current cluster version matches the expected version. AddVoter(id, addr string, version uint64) error @@ -69,9 +72,9 @@ type Consensus interface { // ClusterMembership returns the current cluster membership configuration and associated version. ClusterMembership() (*ClusterMembership, error) - // CommitPayload commits latest unsafe payload to the FSM in a strongly consistent fashion. + // CommitUnsafePayload commits latest unsafe payload to the FSM in a strongly consistent fashion. CommitUnsafePayload(payload *eth.ExecutionPayloadEnvelope) error - // LatestUnsafeBlock returns the latest unsafe payload from FSM in a strongly consistent fashion. + // LatestUnsafePayload returns the latest unsafe payload from FSM in a strongly consistent fashion. LatestUnsafePayload() (*eth.ExecutionPayloadEnvelope, error) // Shutdown shuts down the consensus protocol client. diff --git a/op-conductor/consensus/mocks/Consensus.go b/op-conductor/consensus/mocks/Consensus.go index ca1397a690e1f..902174435146a 100644 --- a/op-conductor/consensus/mocks/Consensus.go +++ b/op-conductor/consensus/mocks/Consensus.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.39.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks @@ -118,6 +118,51 @@ func (_c *Consensus_AddVoter_Call) RunAndReturn(run func(string, string, uint64) return _c } +// Addr provides a mock function with given fields: +func (_m *Consensus) Addr() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Addr") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Consensus_Addr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Addr' +type Consensus_Addr_Call struct { + *mock.Call +} + +// Addr is a helper method to define mock.On call +func (_e *Consensus_Expecter) Addr() *Consensus_Addr_Call { + return &Consensus_Addr_Call{Call: _e.mock.On("Addr")} +} + +func (_c *Consensus_Addr_Call) Run(run func()) *Consensus_Addr_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Consensus_Addr_Call) Return(_a0 string) *Consensus_Addr_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Consensus_Addr_Call) RunAndReturn(run func() string) *Consensus_Addr_Call { + _c.Call.Return(run) + return _c +} + // ClusterMembership provides a mock function with given fields: func (_m *Consensus) ClusterMembership() (*consensus.ClusterMembership, error) { ret := _m.Called() diff --git a/op-conductor/consensus/raft.go b/op-conductor/consensus/raft.go index f6acc0fb76f17..86b32ea00f8c5 100644 --- a/op-conductor/consensus/raft.go +++ b/op-conductor/consensus/raft.go @@ -29,12 +29,30 @@ type RaftConsensus struct { serverID raft.ServerID r *raft.Raft + transport *raft.NetworkTransport + // advertisedAddr is the host & port to contact this server. + // If empty, the address of the transport should be used instead. + advertisedAddr string + unsafeTracker *unsafeHeadTracker } type RaftConsensusConfig struct { - ServerID string - ServerAddr string + ServerID string + + // AdvertisedAddr is the address to advertise, + // i.e. the address external raft peers use to contact us. + // If left empty, it defaults to the resulting + // local address that we bind the underlying transport to. + AdvertisedAddr raft.ServerAddress + + // ListenPort is the port to bind the server to. + // This may be 0, an available port will then be selected by the system. + ListenPort int + // ListenAddr is the address to bind the server to. + // E.g. use 0.0.0.0 to bind to an external-facing network. + ListenAddr string + StorageDir string Bootstrap bool RollupCfg *rollup.Config @@ -86,18 +104,31 @@ func NewRaftConsensus(log log.Logger, cfg *RaftConsensusConfig) (*RaftConsensus, return nil, fmt.Errorf(`raft.NewFileSnapshotStore(%q): %w`, baseDir, err) } - addr, err := net.ResolveTCPAddr("tcp", cfg.ServerAddr) - if err != nil { - return nil, errors.Wrap(err, "failed to resolve tcp address") + var advertiseAddr net.Addr + if cfg.AdvertisedAddr == "" { + log.Warn("No advertised address specified. Advertising local address.") + } else { + x, err := net.ResolveTCPAddr("tcp", string(cfg.AdvertisedAddr)) + if err != nil { + return nil, fmt.Errorf("failed to resolve advertised TCP address %q: %w", string(cfg.AdvertisedAddr), err) + } + advertiseAddr = x + log.Info("Resolved advertising address", "adAddr", cfg.AdvertisedAddr, + "adIP", x.IP, "adPort", x.Port, "adZone", x.Zone) } + bindAddr := fmt.Sprintf("%s:%d", cfg.ListenAddr, cfg.ListenPort) + log.Info("Binding raft server to network transport", "listenAddr", bindAddr) + maxConnPool := 10 timeout := 5 * time.Second - bindAddr := fmt.Sprintf("0.0.0.0:%d", addr.Port) - transport, err := raft.NewTCPTransportWithLogger(bindAddr, addr, maxConnPool, timeout, rc.Logger) + + // When advertiseAddr == nil, the transport will use the local address that it is bound to. + transport, err := raft.NewTCPTransportWithLogger(bindAddr, advertiseAddr, maxConnPool, timeout, rc.Logger) if err != nil { return nil, errors.Wrap(err, "failed to create raft tcp transport") } + log.Info("Raft server network transport is up", "addr", transport.LocalAddr()) fsm := NewUnsafeHeadTracker(log) @@ -110,11 +141,19 @@ func NewRaftConsensus(log log.Logger, cfg *RaftConsensusConfig) (*RaftConsensus, // If bootstrap = true, start raft in bootstrap mode, this will allow the current node to elect itself as leader when there's no other participants // and allow other nodes to join the cluster. if cfg.Bootstrap { + var advertisedAddr raft.ServerAddress + if cfg.AdvertisedAddr == "" { + advertisedAddr = transport.LocalAddr() + } else { + advertisedAddr = cfg.AdvertisedAddr + } + log.Info("Bootstrapping raft consensus cluster with self", "addr", advertisedAddr) + raftCfg := raft.Configuration{ Servers: []raft.Server{ { ID: rc.LocalID, - Address: raft.ServerAddress(cfg.ServerAddr), + Address: advertisedAddr, Suffrage: raft.Voter, }, }, @@ -132,9 +171,20 @@ func NewRaftConsensus(log log.Logger, cfg *RaftConsensusConfig) (*RaftConsensus, serverID: raft.ServerID(cfg.ServerID), unsafeTracker: fsm, rollupCfg: cfg.RollupCfg, + transport: transport, }, nil } +// Addr returns the address to contact this raft consensus server. +// If no explicit address to advertise was configured, +// the local network address that the raft-consensus server is listening on will be used. +func (rc *RaftConsensus) Addr() string { + if rc.advertisedAddr != "" { + return rc.advertisedAddr + } + return string(rc.transport.LocalAddr()) +} + // AddNonVoter implements Consensus, it tries to add a non-voting member into the cluster. func (rc *RaftConsensus) AddNonVoter(id string, addr string, version uint64) error { if err := checkTCPPortOpen(addr); err != nil { diff --git a/op-conductor/consensus/raft_test.go b/op-conductor/consensus/raft_test.go index fbd9c7cb3bc81..9c8ca48247efe 100644 --- a/op-conductor/consensus/raft_test.go +++ b/op-conductor/consensus/raft_test.go @@ -28,7 +28,9 @@ func TestCommitAndRead(t *testing.T) { } raftConsensusConfig := &RaftConsensusConfig{ ServerID: "SequencerA", - ServerAddr: "127.0.0.1:0", + ListenPort: 0, + ListenAddr: "127.0.0.1", // local test, don't bind to external interface + AdvertisedAddr: "", // use local address that the server binds to StorageDir: storageDir, Bootstrap: true, RollupCfg: rollupCfg, diff --git a/op-conductor/flags/flags.go b/op-conductor/flags/flags.go index 249e8a676e079..7c29bfcab2c3a 100644 --- a/op-conductor/flags/flags.go +++ b/op-conductor/flags/flags.go @@ -19,16 +19,22 @@ const EnvVarPrefix = "OP_CONDUCTOR" var ( ConsensusAddr = &cli.StringFlag{ Name: "consensus.addr", - Usage: "Address to listen for consensus connections", + Usage: "Address (excluding port) to listen for consensus connections.", EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "CONSENSUS_ADDR"), Value: "127.0.0.1", } ConsensusPort = &cli.IntFlag{ Name: "consensus.port", - Usage: "Port to listen for consensus connections", + Usage: "Port to listen for consensus connections. May be 0 to let the system select a port.", EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "CONSENSUS_PORT"), Value: 50050, } + AdvertisedFullAddr = &cli.StringFlag{ + Name: "consensus.advertised", + Usage: "Full address (host and port) for other peers to contact the consensus server. Optional: if left empty, the local address is advertised.", + EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "CONSENSUS_ADVERTISED"), + Value: "", + } RaftBootstrap = &cli.BoolFlag{ Name: "raft.bootstrap", Usage: "If this node should bootstrap a new raft cluster", @@ -127,6 +133,7 @@ var requiredFlags = []cli.Flag{ } var optionalFlags = []cli.Flag{ + AdvertisedFullAddr, Paused, RPCEnableProxy, RaftBootstrap, diff --git a/op-conductor/justfile b/op-conductor/justfile new file mode 100644 index 0000000000000..7ee6ef39bcfa0 --- /dev/null +++ b/op-conductor/justfile @@ -0,0 +1,23 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X main.Version=" + VERSION + " " + \ + "") + "'" + +BINARY := "./bin/op-conductor" + +# Build op-conductor binary +op-conductor: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") + +# Generate mocks +generate-mocks: (go_generate "./...") \ No newline at end of file diff --git a/op-conductor/rpc/api.go b/op-conductor/rpc/api.go index b783e062c7a5d..a8b62e3b74fdd 100644 --- a/op-conductor/rpc/api.go +++ b/op-conductor/rpc/api.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-conductor/consensus" @@ -15,10 +16,14 @@ var ErrNotLeader = errors.New("refusing to proxy request to non-leader sequencer // API defines the interface for the op-conductor API. type API interface { - // OverrideLeader is used to override the leader status, this is only used to return true for Leader() & LeaderWithID() calls. + // OverrideLeader is used to override or clear override for the leader status. // It does not impact the actual raft consensus leadership status. It is supposed to be used when the cluster is unhealthy // and the node is the only one up, to allow batcher to be able to connect to the node, so that it could download blocks from the manually started sequencer. - OverrideLeader(ctx context.Context) error + // override: true => force current conductor to be treated as leader regardless of the actual leadership status in raft. + // override: false => clear the override, return the actual leadership status in raft. + OverrideLeader(ctx context.Context, override bool) error + // LeaderOverridden returns true if the leader status is overridden. + LeaderOverridden(ctx context.Context) (bool, error) // Pause pauses op-conductor. Pause(ctx context.Context) error // Resume resumes op-conductor. @@ -57,13 +62,19 @@ type API interface { CommitUnsafePayload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error } -// ExecutionProxyAPI defines the methods proxied to the execution rpc backend +// ExecutionProxyAPI defines the methods proxied to the execution 'eth_' rpc backend // This should include all methods that are called by op-batcher or op-proposer type ExecutionProxyAPI interface { GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) } -// NodeProxyAPI defines the methods proxied to the node rpc backend +// ExecutionMinerProxyAPI defines the methods proxied to the execution 'miner_' rpc backend +// This should include all methods that are called by op-batcher or op-proposer +type ExecutionMinerProxyAPI interface { + SetMaxDASize(ctx context.Context, maxTxSize hexutil.Big, maxBlockSize hexutil.Big) bool +} + +// NodeProxyAPI defines the methods proxied to the node 'optimism_' rpc backend // This should include all methods that are called by op-batcher or op-proposer type NodeProxyAPI interface { OutputAtBlock(ctx context.Context, blockNumString string) (*eth.OutputResponse, error) @@ -71,7 +82,7 @@ type NodeProxyAPI interface { RollupConfig(ctx context.Context) (*rollup.Config, error) } -// NodeProxyAPI defines the methods proxied to the node rpc backend +// NodeAdminProxyAPI defines the methods proxied to the node 'admin_' rpc backend // This should include all methods that are called by op-batcher or op-proposer type NodeAdminProxyAPI interface { SequencerActive(ctx context.Context) (bool, error) diff --git a/op-conductor/rpc/backend.go b/op-conductor/rpc/backend.go index beed37512ed6d..3224bc7f7b1e9 100644 --- a/op-conductor/rpc/backend.go +++ b/op-conductor/rpc/backend.go @@ -2,7 +2,6 @@ package rpc import ( "context" - "sync/atomic" "github.com/ethereum/go-ethereum/log" @@ -11,6 +10,8 @@ import ( ) type conductor interface { + OverrideLeader(override bool) + LeaderOverridden() bool Pause(ctx context.Context) error Resume(ctx context.Context) error Stop(ctx context.Context) error @@ -32,9 +33,8 @@ type conductor interface { // APIBackend is the backend implementation of the API. // TODO: (https://github.com/ethereum-optimism/protocol-quest/issues/45) Add metrics tracer here. type APIBackend struct { - log log.Logger - con conductor - leaderOverride atomic.Bool + log log.Logger + con conductor } // NewAPIBackend creates a new APIBackend instance. @@ -48,11 +48,16 @@ func NewAPIBackend(log log.Logger, con conductor) *APIBackend { var _ API = (*APIBackend)(nil) // OverrideLeader implements API. -func (api *APIBackend) OverrideLeader(ctx context.Context) error { - api.leaderOverride.Store(true) +func (api *APIBackend) OverrideLeader(_ context.Context, override bool) error { + api.con.OverrideLeader(override) return nil } +// LeaderOverridden implements API. +func (api *APIBackend) LeaderOverridden(_ context.Context) (bool, error) { + return api.con.LeaderOverridden(), nil +} + // Paused implements API. func (api *APIBackend) Paused(ctx context.Context) (bool, error) { return api.con.Paused(), nil @@ -90,19 +95,11 @@ func (api *APIBackend) CommitUnsafePayload(ctx context.Context, payload *eth.Exe // Leader implements API, returns true if current conductor is leader of the cluster. func (api *APIBackend) Leader(ctx context.Context) (bool, error) { - return api.leaderOverride.Load() || api.con.Leader(ctx), nil + return api.con.Leader(ctx), nil } // LeaderWithID implements API, returns the leader's server ID and address (not necessarily the current conductor). func (api *APIBackend) LeaderWithID(ctx context.Context) (*consensus.ServerInfo, error) { - if api.leaderOverride.Load() { - return &consensus.ServerInfo{ - ID: "N/A (Leader overridden)", - Addr: "N/A", - Suffrage: 0, - }, nil - } - return api.con.LeaderWithID(ctx), nil } diff --git a/op-conductor/rpc/client.go b/op-conductor/rpc/client.go index 32f7388c60153..0a88cc6538e82 100644 --- a/op-conductor/rpc/client.go +++ b/op-conductor/rpc/client.go @@ -28,8 +28,15 @@ func prefixRPC(method string) string { } // OverrideLeader implements API. -func (c *APIClient) OverrideLeader(ctx context.Context) error { - return c.c.CallContext(ctx, nil, prefixRPC("overrideLeader")) +func (c *APIClient) OverrideLeader(ctx context.Context, override bool) error { + return c.c.CallContext(ctx, nil, prefixRPC("overrideLeader"), override) +} + +// LeaderOverridden implements API. +func (c *APIClient) LeaderOverridden(ctx context.Context) (bool, error) { + var overridden bool + err := c.c.CallContext(ctx, &overridden, prefixRPC("leaderOverridden")) + return overridden, err } // Paused implements API. diff --git a/op-conductor/rpc/excecution_miner_proxy.go b/op-conductor/rpc/excecution_miner_proxy.go new file mode 100644 index 0000000000000..9594e790b3227 --- /dev/null +++ b/op-conductor/rpc/excecution_miner_proxy.go @@ -0,0 +1,40 @@ +package rpc + +import ( + "context" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +var ExecutionMinerRPCNamespace = "miner" + +// ExecutionMinerProxyBackend implements an execution rpc proxy with a leadership check before each call. +type ExecutionMinerProxyBackend struct { + log log.Logger + con conductor + client *ethclient.Client +} + +var _ ExecutionMinerProxyAPI = (*ExecutionMinerProxyBackend)(nil) + +func NewExecutionMinerProxyBackend(log log.Logger, con conductor, client *ethclient.Client) *ExecutionMinerProxyBackend { + return &ExecutionMinerProxyBackend{ + log: log, + con: con, + client: client, + } +} + +func (api *ExecutionMinerProxyBackend) SetMaxDASize(ctx context.Context, maxTxSize hexutil.Big, maxBlockSize hexutil.Big) bool { + var result bool + if !api.con.Leader(ctx) { + return false + } + err := api.client.Client().Call(&result, "miner_setMaxDASize", maxTxSize, maxBlockSize) + if err != nil { + return false + } + return result +} diff --git a/op-deployer/.gitignore b/op-deployer/.gitignore new file mode 100644 index 0000000000000..d2ecf22590b57 --- /dev/null +++ b/op-deployer/.gitignore @@ -0,0 +1,2 @@ +bin +.deployer \ No newline at end of file diff --git a/op-deployer/.goreleaser.yaml b/op-deployer/.goreleaser.yaml new file mode 100644 index 0000000000000..5040ed4bc4d42 --- /dev/null +++ b/op-deployer/.goreleaser.yaml @@ -0,0 +1,77 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +project_name: op-deployer + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + +builds: + - id: main + main: ./cmd/op-deployer + binary: op-deployer + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + - goos: linux + goarch: arm64 + mod_timestamp: "{{ .CommitTimestamp }}" + ldflags: + - -X main.GitCommit={{ .FullCommit }} + - -X main.GitDate={{ .CommitDate }} + - -X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Version={{ .Version }} + - -X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Meta="unstable" + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: "{{ .ProjectName }}-{{.Version}}-{{ tolower .Os }}-{{ .Arch }}" + # use zip for windows archives + wrap_in_directory: true + format_overrides: + - goos: windows + format: zip + +dockers: + - id: default + goos: linux + goarch: amd64 + dockerfile: Dockerfile.default + image_templates: + - "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:{{ .Tag }}" + - id: minimal + goos: linux + goarch: amd64 + dockerfile: Dockerfile.minimal + image_templates: + - "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:{{ .Tag }}-minimal" + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + +release: + github: + owner: ethereum-optimism + name: optimism + make_latest: false + +monorepo: + tag_prefix: op-deployer/ + dir: op-deployer \ No newline at end of file diff --git a/op-deployer/Dockerfile.default b/op-deployer/Dockerfile.default new file mode 100644 index 0000000000000..0821bc0c48ee4 --- /dev/null +++ b/op-deployer/Dockerfile.default @@ -0,0 +1,9 @@ +FROM debian:bookworm-20240812-slim +ENTRYPOINT ["/op-deployer"] +COPY op-deployer /op-deployer + +# Install ca-certificates so that HTTPS requests work +RUN apt-get update && apt-get install -y ca-certificates + +# Symlink onto the PATH +RUN ln -s /op-deployer /usr/local/bin/op-deployer \ No newline at end of file diff --git a/op-deployer/Dockerfile.minimal b/op-deployer/Dockerfile.minimal new file mode 100644 index 0000000000000..9e8811980090c --- /dev/null +++ b/op-deployer/Dockerfile.minimal @@ -0,0 +1,3 @@ +FROM scratch +ENTRYPOINT ["/op-deployer"] +COPY op-deployer /op-deployer \ No newline at end of file diff --git a/op-chain-ops/cmd/op-deployer/main.go b/op-deployer/cmd/op-deployer/main.go similarity index 82% rename from op-chain-ops/cmd/op-deployer/main.go rename to op-deployer/cmd/op-deployer/main.go index d6daf959c1033..6c3b1fa0807d1 100644 --- a/op-chain-ops/cmd/op-deployer/main.go +++ b/op-deployer/cmd/op-deployer/main.go @@ -4,14 +4,13 @@ import ( "fmt" "os" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/bootstrap" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/bootstrap" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/version" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version" opservice "github.com/ethereum-optimism/optimism/op-service" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/inspect" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" "github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/urfave/cli/v2" ) diff --git a/op-deployer/justfile b/op-deployer/justfile new file mode 100644 index 0000000000000..740aa7c2ceb4e --- /dev/null +++ b/op-deployer/justfile @@ -0,0 +1,10 @@ +build: + go build -o bin/op-deployer cmd/op-deployer/main.go + +download-artifacts checksum outfile: + curl -o {{outfile}} -L https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-{{checksum}}.tar.gz + +calculate-artifacts-hash checksum: + just download-artifacts {{checksum}} /tmp/artifact.tgz + sha256sum /tmp/artifact.tgz + rm /tmp/artifact.tgz \ No newline at end of file diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go new file mode 100644 index 0000000000000..b4ff343de351a --- /dev/null +++ b/op-deployer/pkg/deployer/apply.go @@ -0,0 +1,344 @@ +package deployer + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "strings" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +type ApplyConfig struct { + L1RPCUrl string + Workdir string + PrivateKey string + Logger log.Logger + + privateKeyECDSA *ecdsa.PrivateKey +} + +func (a *ApplyConfig) Check() error { + if a.Workdir == "" { + return fmt.Errorf("workdir must be specified") + } + + if a.PrivateKey != "" { + privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(a.PrivateKey, "0x")) + if err != nil { + return fmt.Errorf("failed to parse private key: %w", err) + } + a.privateKeyECDSA = privECDSA + } + + if a.Logger == nil { + return fmt.Errorf("logger must be specified") + } + + return nil +} + +func (a *ApplyConfig) CheckLive() error { + if a.privateKeyECDSA == nil { + return fmt.Errorf("private key must be specified") + } + + if a.L1RPCUrl == "" { + return fmt.Errorf("l1RPCUrl must be specified") + } + + return nil +} + +func ApplyCLI() func(cliCtx *cli.Context) error { + return func(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + l1RPCUrl := cliCtx.String(L1RPCURLFlagName) + workdir := cliCtx.String(WorkdirFlagName) + privateKey := cliCtx.String(PrivateKeyFlagName) + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + return Apply(ctx, ApplyConfig{ + L1RPCUrl: l1RPCUrl, + Workdir: workdir, + PrivateKey: privateKey, + Logger: l, + }) + } +} + +func Apply(ctx context.Context, cfg ApplyConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for apply: %w", err) + } + + intent, err := pipeline.ReadIntent(cfg.Workdir) + if err != nil { + return fmt.Errorf("failed to read intent: %w", err) + } + + st, err := pipeline.ReadState(cfg.Workdir) + if err != nil { + return fmt.Errorf("failed to read state: %w", err) + } + + if err := ApplyPipeline(ctx, ApplyPipelineOpts{ + L1RPCUrl: cfg.L1RPCUrl, + DeployerPrivateKey: cfg.privateKeyECDSA, + Intent: intent, + State: st, + Logger: cfg.Logger, + StateWriter: pipeline.WorkdirStateWriter(cfg.Workdir), + }); err != nil { + return err + } + + return nil +} + +type pipelineStage struct { + name string + apply func() error +} + +type ApplyPipelineOpts struct { + L1RPCUrl string + DeployerPrivateKey *ecdsa.PrivateKey + Intent *state.Intent + State *state.State + Logger log.Logger + StateWriter pipeline.StateWriter +} + +func ApplyPipeline( + ctx context.Context, + opts ApplyPipelineOpts, +) error { + intent := opts.Intent + if err := intent.Check(); err != nil { + return err + } + st := opts.State + + progressor := func(curr, total int64) { + opts.Logger.Info("artifacts download progress", "current", curr, "total", total) + } + + l1ArtifactsFS, cleanupL1, err := artifacts.Download(ctx, intent.L1ContractsLocator, progressor) + if err != nil { + return fmt.Errorf("failed to download L1 artifacts: %w", err) + } + defer func() { + if err := cleanupL1(); err != nil { + opts.Logger.Warn("failed to clean up L1 artifacts", "err", err) + } + }() + + l2ArtifactsFS, cleanupL2, err := artifacts.Download(ctx, intent.L2ContractsLocator, progressor) + if err != nil { + return fmt.Errorf("failed to download L2 artifacts: %w", err) + } + defer func() { + if err := cleanupL2(); err != nil { + opts.Logger.Warn("failed to clean up L2 artifacts", "err", err) + } + }() + + bundle := pipeline.ArtifactsBundle{ + L1: l1ArtifactsFS, + L2: l2ArtifactsFS, + } + + var deployer common.Address + var bcaster broadcaster.Broadcaster + var l1Client *ethclient.Client + var l1Host *script.Host + if intent.DeploymentStrategy == state.DeploymentStrategyLive { + l1RPC, err := rpc.Dial(opts.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + l1Client = ethclient.NewClient(l1RPC) + + chainID, err := l1Client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get chain ID: %w", err) + } + + signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(opts.DeployerPrivateKey, chainID)) + deployer = crypto.PubkeyToAddress(opts.DeployerPrivateKey.PublicKey) + + bcaster, err = broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ + Logger: opts.Logger, + ChainID: new(big.Int).SetUint64(intent.L1ChainID), + Client: l1Client, + Signer: signer, + From: deployer, + }) + if err != nil { + return fmt.Errorf("failed to create broadcaster: %w", err) + } + + l1Host, err = env.DefaultScriptHost( + bcaster, + opts.Logger, + deployer, + bundle.L1, + script.WithForkHook(func(cfg *script.ForkConfig) (forking.ForkSource, error) { + src, err := forking.RPCSourceByNumber(cfg.URLOrAlias, l1RPC, *cfg.BlockNumber) + if err != nil { + return nil, fmt.Errorf("failed to create RPC fork source: %w", err) + } + return forking.Cache(src), nil + }), + ) + if err != nil { + return fmt.Errorf("failed to create L1 script host: %w", err) + } + + latest, err := l1Client.HeaderByNumber(ctx, nil) + if err != nil { + return fmt.Errorf("failed to get latest block: %w", err) + } + + if _, err := l1Host.CreateSelectFork( + script.ForkWithURLOrAlias("main"), + script.ForkWithBlockNumberU256(latest.Number), + ); err != nil { + return fmt.Errorf("failed to select fork: %w", err) + } + } else { + deployer = common.Address{0x01} + bcaster = broadcaster.NoopBroadcaster() + l1Host, err = env.DefaultScriptHost( + bcaster, + opts.Logger, + deployer, + bundle.L1, + ) + if err != nil { + return fmt.Errorf("failed to create L1 script host: %w", err) + } + } + + pEnv := &pipeline.Env{ + StateWriter: opts.StateWriter, + L1ScriptHost: l1Host, + L1Client: l1Client, + Logger: opts.Logger, + Broadcaster: bcaster, + Deployer: deployer, + } + + pline := []pipelineStage{ + {"init", func() error { + if intent.DeploymentStrategy == state.DeploymentStrategyLive { + return pipeline.InitLiveStrategy(ctx, pEnv, intent, st) + } else { + return pipeline.InitGenesisStrategy(pEnv, intent, st) + } + }}, + {"deploy-superchain", func() error { + return pipeline.DeploySuperchain(pEnv, intent, st) + }}, + {"deploy-implementations", func() error { + return pipeline.DeployImplementations(pEnv, intent, st) + }}, + } + + // Deploy all OP Chains first. + for _, chain := range intent.Chains { + chainID := chain.ID + pline = append(pline, pipelineStage{ + fmt.Sprintf("deploy-opchain-%s", chainID.Hex()), + func() error { + return pipeline.DeployOPChain(pEnv, intent, st, chainID) + }, + }, pipelineStage{ + fmt.Sprintf("deploy-alt-da-%s", chainID.Hex()), + func() error { + return pipeline.DeployAltDA(pEnv, intent, st, chainID) + }, + }, pipelineStage{ + fmt.Sprintf("generate-l2-genesis-%s", chainID.Hex()), + func() error { + return pipeline.GenerateL2Genesis(pEnv, intent, bundle, st, chainID) + }, + }) + } + + // Set start block after all OP chains have been deployed, since the + // genesis strategy requires all the OP chains to exist in genesis. + for _, chain := range intent.Chains { + chainID := chain.ID + pline = append(pline, pipelineStage{ + fmt.Sprintf("set-start-block-%s", chainID.Hex()), + func() error { + if intent.DeploymentStrategy == state.DeploymentStrategyLive { + return pipeline.SetStartBlockLiveStrategy(ctx, pEnv, st, chainID) + } else { + return pipeline.SetStartBlockGenesisStrategy(pEnv, st, chainID) + } + }, + }) + } + + // Run through the pipeline. The state dump is captured between + // every step. + for _, stage := range pline { + if err := stage.apply(); err != nil { + return fmt.Errorf("error in pipeline stage apply: %w", err) + } + + if intent.DeploymentStrategy == state.DeploymentStrategyGenesis { + dump, err := pEnv.L1ScriptHost.StateDump() + if err != nil { + return fmt.Errorf("failed to dump state: %w", err) + } + st.L1StateDump = &state.GzipData[foundry.ForgeAllocs]{ + Data: dump, + } + } + + if _, err := pEnv.Broadcaster.Broadcast(ctx); err != nil { + return fmt.Errorf("failed to broadcast stage %s: %w", stage.name, err) + } + if err := pEnv.StateWriter.WriteState(st); err != nil { + return fmt.Errorf("failed to write state: %w", err) + } + } + + st.AppliedIntent = intent + if err := pEnv.StateWriter.WriteState(st); err != nil { + return fmt.Errorf("failed to write state: %w", err) + } + + return nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/asterisc.go b/op-deployer/pkg/deployer/bootstrap/asterisc.go new file mode 100644 index 0000000000000..e7cd4e6036c9b --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/asterisc.go @@ -0,0 +1,178 @@ +package bootstrap + +import ( + "context" + "crypto/ecdsa" + "fmt" + "strings" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" +) + +type AsteriscConfig struct { + L1RPCUrl string + PrivateKey string + Logger log.Logger + ArtifactsLocator *artifacts.Locator + + privateKeyECDSA *ecdsa.PrivateKey + + PreimageOracle common.Address +} + +func (c *AsteriscConfig) Check() error { + if c.L1RPCUrl == "" { + return fmt.Errorf("l1RPCUrl must be specified") + } + + if c.PrivateKey == "" { + return fmt.Errorf("private key must be specified") + } + + privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x")) + if err != nil { + return fmt.Errorf("failed to parse private key: %w", err) + } + c.privateKeyECDSA = privECDSA + + if c.Logger == nil { + return fmt.Errorf("logger must be specified") + } + + if c.ArtifactsLocator == nil { + return fmt.Errorf("artifacts locator must be specified") + } + + if c.PreimageOracle == (common.Address{}) { + return fmt.Errorf("preimage oracle must be specified") + } + + return nil +} + +func AsteriscCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) + privateKey := cliCtx.String(deployer.PrivateKeyFlagName) + artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsLocator := new(artifacts.Locator) + if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { + return fmt.Errorf("failed to parse artifacts URL: %w", err) + } + + preimageOracle := common.HexToAddress(cliCtx.String(PreimageOracleFlagName)) + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + return Asterisc(ctx, AsteriscConfig{ + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + Logger: l, + ArtifactsLocator: artifactsLocator, + PreimageOracle: preimageOracle, + }) +} + +func Asterisc(ctx context.Context, cfg AsteriscConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for Asterisc: %w", err) + } + + lgr := cfg.Logger + progressor := func(curr, total int64) { + lgr.Info("artifacts download progress", "current", curr, "total", total) + } + + artifactsFS, cleanup, err := artifacts.Download(ctx, cfg.ArtifactsLocator, progressor) + if err != nil { + return fmt.Errorf("failed to download artifacts: %w", err) + } + defer func() { + if err := cleanup(); err != nil { + lgr.Warn("failed to clean up artifacts", "err", err) + } + }() + + l1Client, err := ethclient.Dial(cfg.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + chainID, err := l1Client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get chain ID: %w", err) + } + + signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID)) + chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey) + + bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ + Logger: lgr, + ChainID: chainID, + Client: l1Client, + Signer: signer, + From: chainDeployer, + }) + if err != nil { + return fmt.Errorf("failed to create broadcaster: %w", err) + } + + l1RPC, err := rpc.Dial(cfg.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + l1Host, err := env.DefaultForkedScriptHost( + ctx, + bcaster, + lgr, + chainDeployer, + artifactsFS, + l1RPC, + ) + if err != nil { + return fmt.Errorf("failed to create script host: %w", err) + } + + dgo, err := opcm.DeployAsterisc( + l1Host, + opcm.DeployAsteriscInput{ + PreimageOracle: cfg.PreimageOracle, + }, + ) + if err != nil { + return fmt.Errorf("error deploying asterisc VM: %w", err) + } + + if _, err := bcaster.Broadcast(ctx); err != nil { + return fmt.Errorf("failed to broadcast: %w", err) + } + + lgr.Info("deployed asterisc VM") + + if err := jsonutil.WriteJSON(dgo, ioutil.ToStdOut()); err != nil { + return fmt.Errorf("failed to write output: %w", err) + } + return nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/delayed_weth.go b/op-deployer/pkg/deployer/bootstrap/delayed_weth.go new file mode 100644 index 0000000000000..9c3e0bc07fabe --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/delayed_weth.go @@ -0,0 +1,214 @@ +package bootstrap + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "strings" + + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" +) + +type DelayedWETHConfig struct { + L1RPCUrl string + PrivateKey string + Logger log.Logger + ArtifactsLocator *artifacts2.Locator + DelayedWethImpl common.Address + + privateKeyECDSA *ecdsa.PrivateKey +} + +func (c *DelayedWETHConfig) Check() error { + if c.L1RPCUrl == "" { + return fmt.Errorf("l1RPCUrl must be specified") + } + + if c.PrivateKey == "" { + return fmt.Errorf("private key must be specified") + } + + privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x")) + if err != nil { + return fmt.Errorf("failed to parse private key: %w", err) + } + c.privateKeyECDSA = privECDSA + + if c.Logger == nil { + return fmt.Errorf("logger must be specified") + } + + if c.ArtifactsLocator == nil { + return fmt.Errorf("artifacts locator must be specified") + } + + return nil +} + +func DelayedWETHCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + config, err := NewDelayedWETHConfigFromClI(cliCtx, l) + if err != nil { + return err + } + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + return DelayedWETH(ctx, config) +} + +func NewDelayedWETHConfigFromClI(cliCtx *cli.Context, l log.Logger) (DelayedWETHConfig, error) { + l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) + privateKey := cliCtx.String(deployer.PrivateKeyFlagName) + artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsLocator := new(artifacts2.Locator) + if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { + return DelayedWETHConfig{}, fmt.Errorf("failed to parse artifacts URL: %w", err) + } + delayedWethImpl := common.HexToAddress(cliCtx.String(DelayedWethImplFlagName)) + config := DelayedWETHConfig{ + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + Logger: l, + ArtifactsLocator: artifactsLocator, + DelayedWethImpl: delayedWethImpl, + } + return config, nil +} + +func DelayedWETH(ctx context.Context, cfg DelayedWETHConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for DelayedWETH: %w", err) + } + + lgr := cfg.Logger + progressor := func(curr, total int64) { + lgr.Info("artifacts download progress", "current", curr, "total", total) + } + + artifactsFS, cleanup, err := artifacts2.Download(ctx, cfg.ArtifactsLocator, progressor) + if err != nil { + return fmt.Errorf("failed to download artifacts: %w", err) + } + defer func() { + if err := cleanup(); err != nil { + lgr.Warn("failed to clean up artifacts", "err", err) + } + }() + + l1Client, err := ethclient.Dial(cfg.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + chainID, err := l1Client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get chain ID: %w", err) + } + chainIDU64 := chainID.Uint64() + + superCfg, err := standard.SuperchainFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting superchain config: %w", err) + } + proxyAdmin, err := standard.ManagerOwnerAddrFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting superchain proxy admin: %w", err) + } + delayedWethOwner, err := standard.SystemOwnerAddrFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting superchain system owner: %w", err) + } + + signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID)) + chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey) + + bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ + Logger: lgr, + ChainID: chainID, + Client: l1Client, + Signer: signer, + From: chainDeployer, + }) + if err != nil { + return fmt.Errorf("failed to create broadcaster: %w", err) + } + + l1RPC, err := rpc.Dial(cfg.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + host, err := env.DefaultForkedScriptHost( + ctx, + bcaster, + lgr, + chainDeployer, + artifactsFS, + l1RPC, + ) + if err != nil { + return fmt.Errorf("failed to create script host: %w", err) + } + + var release string + if cfg.ArtifactsLocator.IsTag() { + release = cfg.ArtifactsLocator.Tag + } else { + release = "dev" + } + + lgr.Info("deploying DelayedWETH", "release", release) + + superchainConfigAddr := common.Address(*superCfg.Config.SuperchainConfigAddr) + + dwo, err := opcm.DeployDelayedWETH( + host, + opcm.DeployDelayedWETHInput{ + Release: release, + ProxyAdmin: proxyAdmin, + SuperchainConfigProxy: superchainConfigAddr, + DelayedWethImpl: cfg.DelayedWethImpl, + DelayedWethOwner: delayedWethOwner, + DelayedWethDelay: big.NewInt(604800), + }, + ) + if err != nil { + return fmt.Errorf("error deploying DelayedWETH: %w", err) + } + + if _, err := bcaster.Broadcast(ctx); err != nil { + return fmt.Errorf("failed to broadcast: %w", err) + } + + lgr.Info("deployed DelayedWETH") + + if err := jsonutil.WriteJSON(dwo, ioutil.ToStdOut()); err != nil { + return fmt.Errorf("failed to write output: %w", err) + } + return nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/delayed_weth_test.go b/op-deployer/pkg/deployer/bootstrap/delayed_weth_test.go new file mode 100644 index 0000000000000..5e3b667b9b2aa --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/delayed_weth_test.go @@ -0,0 +1,45 @@ +package bootstrap + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" +) + +func TestNewDelayedWETHConfigFromCLI(t *testing.T) { + ctx, err := parseCLIArgs(DelayedWETHFlags, + "--artifacts-locator", "tag://op-contracts/v1.6.0", + "--l1-rpc-url", "http://foo", + "--private-key", "0x123456") + require.NoError(t, err) + + logger := testlog.Logger(t, log.LvlInfo) + cfg, err := NewDelayedWETHConfigFromClI(ctx, logger) + require.NoError(t, err) + require.Same(t, logger, cfg.Logger) + require.Equal(t, "op-contracts/v1.6.0", cfg.ArtifactsLocator.Tag) + require.True(t, cfg.ArtifactsLocator.IsTag()) + require.Equal(t, "0x123456", cfg.PrivateKey) +} + +func parseCLIArgs(flags []cli.Flag, args ...string) (*cli.Context, error) { + app := cli.NewApp() + app.Flags = cliapp.ProtectFlags(flags) + var ctx *cli.Context + app.Action = func(c *cli.Context) error { + ctx = c + return nil + } + argsWithCmd := make([]string, len(args)+1) + argsWithCmd[0] = "bootstrap" + copy(argsWithCmd[1:], args) + err := app.Run(argsWithCmd) + if err != nil { + return nil, err + } + return ctx, nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/dispute_game.go b/op-deployer/pkg/deployer/bootstrap/dispute_game.go new file mode 100644 index 0000000000000..3f1d354f28fc1 --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/dispute_game.go @@ -0,0 +1,234 @@ +package bootstrap + +import ( + "context" + "crypto/ecdsa" + "fmt" + "strings" + + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +type DisputeGameConfig struct { + L1RPCUrl string + PrivateKey string + Logger log.Logger + ArtifactsLocator *artifacts2.Locator + + privateKeyECDSA *ecdsa.PrivateKey + + Vm common.Address + GameKind string + GameType uint32 + AbsolutePrestate common.Hash + MaxGameDepth uint64 + SplitDepth uint64 + ClockExtension uint64 + MaxClockDuration uint64 + DelayedWethProxy common.Address + AnchorStateRegistryProxy common.Address + L2ChainId uint64 + Proposer common.Address + Challenger common.Address +} + +func (c *DisputeGameConfig) Check() error { + if c.L1RPCUrl == "" { + return fmt.Errorf("l1RPCUrl must be specified") + } + + if c.PrivateKey == "" { + return fmt.Errorf("private key must be specified") + } + + privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x")) + if err != nil { + return fmt.Errorf("failed to parse private key: %w", err) + } + c.privateKeyECDSA = privECDSA + + if c.Logger == nil { + return fmt.Errorf("logger must be specified") + } + + if c.ArtifactsLocator == nil { + return fmt.Errorf("artifacts locator must be specified") + } + + return nil +} + +func DisputeGameCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + cfg, err := NewDisputeGameConfigFromCLI(cliCtx, l) + if err != nil { + return err + } + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + return DisputeGame(ctx, cfg) +} + +func NewDisputeGameConfigFromCLI(cliCtx *cli.Context, l log.Logger) (DisputeGameConfig, error) { + l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) + privateKey := cliCtx.String(deployer.PrivateKeyFlagName) + artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsLocator := new(artifacts2.Locator) + if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { + return DisputeGameConfig{}, fmt.Errorf("failed to parse artifacts URL: %w", err) + } + + cfg := DisputeGameConfig{ + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + Logger: l, + ArtifactsLocator: artifactsLocator, + + Vm: common.HexToAddress(cliCtx.String(VmFlagName)), + GameKind: cliCtx.String(GameKindFlagName), + GameType: uint32(cliCtx.Uint64(GameTypeFlagName)), + AbsolutePrestate: common.HexToHash(cliCtx.String(AbsolutePrestateFlagName)), + MaxGameDepth: cliCtx.Uint64(MaxGameDepthFlagName), + SplitDepth: cliCtx.Uint64(SplitDepthFlagName), + ClockExtension: cliCtx.Uint64(ClockExtensionFlagName), + MaxClockDuration: cliCtx.Uint64(MaxClockDurationFlagName), + DelayedWethProxy: common.HexToAddress(cliCtx.String(DelayedWethProxyFlagName)), + AnchorStateRegistryProxy: common.HexToAddress(cliCtx.String(AnchorStateRegistryProxyFlagName)), + L2ChainId: cliCtx.Uint64(L2ChainIdFlagName), + Proposer: common.HexToAddress(cliCtx.String(ProposerFlagName)), + Challenger: common.HexToAddress(cliCtx.String(ChallengerFlagName)), + } + return cfg, nil +} + +func DisputeGame(ctx context.Context, cfg DisputeGameConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for DisputeGame: %w", err) + } + + lgr := cfg.Logger + progressor := func(curr, total int64) { + lgr.Info("artifacts download progress", "current", curr, "total", total) + } + + artifactsFS, cleanup, err := artifacts2.Download(ctx, cfg.ArtifactsLocator, progressor) + if err != nil { + return fmt.Errorf("failed to download artifacts: %w", err) + } + defer func() { + if err := cleanup(); err != nil { + lgr.Warn("failed to clean up artifacts", "err", err) + } + }() + + l1Client, err := ethclient.Dial(cfg.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + l1Rpc, err := rpc.Dial(cfg.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + chainID, err := l1Client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get chain ID: %w", err) + } + chainIDU64 := chainID.Uint64() + + standardVersionsTOML, err := standard.L1VersionsDataFor(chainIDU64) + if err != nil { + return fmt.Errorf("error getting standard versions TOML: %w", err) + } + + signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID)) + chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey) + + bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ + Logger: lgr, + ChainID: chainID, + Client: l1Client, + Signer: signer, + From: chainDeployer, + }) + if err != nil { + return fmt.Errorf("failed to create broadcaster: %w", err) + } + + host, err := env.DefaultForkedScriptHost( + ctx, + bcaster, + lgr, + chainDeployer, + artifactsFS, + l1Rpc, + ) + if err != nil { + return fmt.Errorf("failed to create L1 script host: %w", err) + } + + var release string + if cfg.ArtifactsLocator.IsTag() { + release = cfg.ArtifactsLocator.Tag + } else { + release = "dev" + } + + lgr.Info("deploying dispute game", "release", release) + dgo, err := opcm.DeployDisputeGame( + host, + opcm.DeployDisputeGameInput{ + Release: release, + StandardVersionsToml: standardVersionsTOML, + VmAddress: cfg.Vm, + GameKind: cfg.GameKind, + GameType: cfg.GameType, + AbsolutePrestate: cfg.AbsolutePrestate, + MaxGameDepth: cfg.MaxGameDepth, + SplitDepth: cfg.SplitDepth, + ClockExtension: cfg.ClockExtension, + MaxClockDuration: cfg.MaxClockDuration, + DelayedWethProxy: cfg.DelayedWethProxy, + AnchorStateRegistryProxy: cfg.AnchorStateRegistryProxy, + L2ChainId: cfg.L2ChainId, + Proposer: cfg.Proposer, + Challenger: cfg.Challenger, + }, + ) + if err != nil { + return fmt.Errorf("error deploying dispute game: %w", err) + } + + if _, err := bcaster.Broadcast(ctx); err != nil { + return fmt.Errorf("failed to broadcast: %w", err) + } + + lgr.Info("deployed dispute game") + + if err := jsonutil.WriteJSON(dgo, ioutil.ToStdOut()); err != nil { + return fmt.Errorf("failed to write output: %w", err) + } + return nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/dispute_game_test.go b/op-deployer/pkg/deployer/bootstrap/dispute_game_test.go new file mode 100644 index 0000000000000..e24ae3754dfe9 --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/dispute_game_test.go @@ -0,0 +1,66 @@ +package bootstrap + +import ( + "reflect" + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestNewDisputeGameConfigFromCLI(t *testing.T) { + ctx, err := parseCLIArgs(DisputeGameFlags, + "--artifacts-locator", "tag://op-contracts/v1.6.0", + "--l1-rpc-url", "http://foo", + "--private-key", "0x123456", + + "--game-type", "2", + "--delayed-weth-proxy", common.Address{0xaa}.Hex(), + "--anchor-state-registry-proxy", common.Address{0xbb}.Hex(), + "--l2-chain-id", "901", + "--proposer", common.Address{0xcc}.Hex(), + "--challenger", common.Address{0xdd}.Hex(), + "--vm", common.Address{0xee}.Hex(), + ) + require.NoError(t, err) + + logger := testlog.Logger(t, log.LvlInfo) + cfg, err := NewDisputeGameConfigFromCLI(ctx, logger) + require.NoError(t, err) + require.Same(t, logger, cfg.Logger) + require.Equal(t, "op-contracts/v1.6.0", cfg.ArtifactsLocator.Tag) + require.True(t, cfg.ArtifactsLocator.IsTag()) + require.Equal(t, "0x123456", cfg.PrivateKey) + require.Equal(t, "FaultDisputeGame", cfg.GameKind) + require.Equal(t, uint32(2), cfg.GameType) + require.Equal(t, standard.DisputeAbsolutePrestate, cfg.AbsolutePrestate) + require.Equal(t, standard.DisputeMaxGameDepth, cfg.MaxGameDepth) + require.Equal(t, standard.DisputeSplitDepth, cfg.SplitDepth) + require.Equal(t, standard.DisputeClockExtension, cfg.ClockExtension) + require.Equal(t, standard.DisputeMaxClockDuration, cfg.MaxClockDuration) + require.Equal(t, common.Address{0xaa}, cfg.DelayedWethProxy) + require.Equal(t, common.Address{0xbb}, cfg.AnchorStateRegistryProxy) + require.Equal(t, common.Address{0xcc}, cfg.Proposer) + require.Equal(t, common.Address{0xdd}, cfg.Challenger) + require.Equal(t, common.Address{0xee}, cfg.Vm) + require.Equal(t, uint64(901), cfg.L2ChainId) + + // Check all fields are set to ensure any newly added fields don't get missed. + cfgRef := reflect.ValueOf(cfg) + cfgType := reflect.TypeOf(cfg) + var unsetFields []string + for i := 0; i < cfgRef.NumField(); i++ { + field := cfgType.Field(i) + if field.Type == reflect.TypeOf(cfg.privateKeyECDSA) { + // privateKeyECDSA is only set when Check() is called so skip it. + continue + } + if cfgRef.Field(i).IsZero() { + unsetFields = append(unsetFields, field.Name) + } + } + require.Empty(t, unsetFields, "Found unset fields in config") +} diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go new file mode 100644 index 0000000000000..f6a130f075a0c --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -0,0 +1,267 @@ +package bootstrap + +import ( + "errors" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum/go-ethereum/common" + "github.com/urfave/cli/v2" +) + +const ( + ArtifactsLocatorFlagName = "artifacts-locator" + WithdrawalDelaySecondsFlagName = "withdrawal-delay-seconds" + MinProposalSizeBytesFlagName = "min-proposal-size-bytes" + ChallengePeriodSecondsFlagName = "challenge-period-seconds" + ProofMaturityDelaySecondsFlagName = "proof-maturity-delay-seconds" + DisputeGameFinalityDelaySecondsFlagName = "dispute-game-finality-delay-seconds" + MIPSVersionFlagName = "mips-version" + VmFlagName = "vm" + GameKindFlagName = "game-kind" + GameTypeFlagName = "game-type" + AbsolutePrestateFlagName = "absolute-prestate" + MaxGameDepthFlagName = "max-game-depth" + SplitDepthFlagName = "split-depth" + ClockExtensionFlagName = "clock-extension" + MaxClockDurationFlagName = "max-clock-duration" + AnchorStateRegistryProxyFlagName = "anchor-state-registry-proxy" + L2ChainIdFlagName = "l2-chain-id" + ProposerFlagName = "proposer" + ChallengerFlagName = "challenger" + PreimageOracleFlagName = "preimage-oracle" + ReleaseFlagName = "release" + DelayedWethProxyFlagName = "delayed-weth-proxy" + DelayedWethImplFlagName = "delayed-weth-impl" +) + +var ( + ArtifactsLocatorFlag = &cli.StringFlag{ + Name: ArtifactsLocatorFlagName, + Usage: "Locator for artifacts.", + EnvVars: deployer.PrefixEnvVar("ARTIFACTS_LOCATOR"), + } + WithdrawalDelaySecondsFlag = &cli.Uint64Flag{ + Name: WithdrawalDelaySecondsFlagName, + Usage: "Withdrawal delay in seconds.", + EnvVars: deployer.PrefixEnvVar("WITHDRAWAL_DELAY_SECONDS"), + Value: standard.WithdrawalDelaySeconds, + } + MinProposalSizeBytesFlag = &cli.Uint64Flag{ + Name: MinProposalSizeBytesFlagName, + Usage: "Minimum proposal size in bytes.", + EnvVars: deployer.PrefixEnvVar("MIN_PROPOSAL_SIZE_BYTES"), + Value: standard.MinProposalSizeBytes, + } + ChallengePeriodSecondsFlag = &cli.Uint64Flag{ + Name: ChallengePeriodSecondsFlagName, + Usage: "Challenge period in seconds.", + EnvVars: deployer.PrefixEnvVar("CHALLENGE_PERIOD_SECONDS"), + Value: standard.ChallengePeriodSeconds, + } + ProofMaturityDelaySecondsFlag = &cli.Uint64Flag{ + Name: ProofMaturityDelaySecondsFlagName, + Usage: "Proof maturity delay in seconds.", + EnvVars: deployer.PrefixEnvVar("PROOF_MATURITY_DELAY_SECONDS"), + Value: standard.ProofMaturityDelaySeconds, + } + DisputeGameFinalityDelaySecondsFlag = &cli.Uint64Flag{ + Name: DisputeGameFinalityDelaySecondsFlagName, + Usage: "Dispute game finality delay in seconds.", + EnvVars: deployer.PrefixEnvVar("DISPUTE_GAME_FINALITY_DELAY_SECONDS"), + Value: standard.DisputeGameFinalityDelaySeconds, + } + MIPSVersionFlag = &cli.Uint64Flag{ + Name: MIPSVersionFlagName, + Usage: "MIPS version.", + EnvVars: deployer.PrefixEnvVar("MIPS_VERSION"), + Value: standard.MIPSVersion, + } + VmFlag = &cli.StringFlag{ + Name: VmFlagName, + Usage: "VM contract address.", + EnvVars: deployer.PrefixEnvVar("VM"), + } + GameKindFlag = &cli.StringFlag{ + Name: GameKindFlagName, + Usage: "Game kind (FaultDisputeGame or PermissionedDisputeGame).", + EnvVars: deployer.PrefixEnvVar("GAME_KIND"), + Value: "FaultDisputeGame", + } + GameTypeFlag = &cli.StringFlag{ + Name: GameTypeFlagName, + Usage: "Game type (integer or fractional).", + EnvVars: deployer.PrefixEnvVar("GAME_TYPE"), + } + AbsolutePrestateFlag = &cli.StringFlag{ + Name: AbsolutePrestateFlagName, + Usage: "Absolute prestate.", + EnvVars: deployer.PrefixEnvVar("ABSOLUTE_PRESTATE"), + Value: standard.DisputeAbsolutePrestate.Hex(), + } + MaxGameDepthFlag = &cli.Uint64Flag{ + Name: MaxGameDepthFlagName, + Usage: "Max game depth.", + EnvVars: deployer.PrefixEnvVar("MAX_GAME_DEPTH"), + Value: standard.DisputeMaxGameDepth, + } + SplitDepthFlag = &cli.Uint64Flag{ + Name: SplitDepthFlagName, + Usage: "Split depth.", + EnvVars: deployer.PrefixEnvVar("SPLIT_DEPTH"), + Value: standard.DisputeSplitDepth, + } + ClockExtensionFlag = &cli.Uint64Flag{ + Name: ClockExtensionFlagName, + Usage: "Clock extension.", + EnvVars: deployer.PrefixEnvVar("CLOCK_EXTENSION"), + Value: standard.DisputeClockExtension, + } + MaxClockDurationFlag = &cli.Uint64Flag{ + Name: MaxClockDurationFlagName, + Usage: "Max clock duration.", + EnvVars: deployer.PrefixEnvVar("MAX_CLOCK_DURATION"), + Value: standard.DisputeMaxClockDuration, + } + DelayedWethProxyFlag = &cli.StringFlag{ + Name: DelayedWethProxyFlagName, + Usage: "Delayed WETH proxy.", + EnvVars: deployer.PrefixEnvVar("DELAYED_WETH_PROXY"), + } + DelayedWethImplFlag = &cli.StringFlag{ + Name: DelayedWethImplFlagName, + Usage: "Delayed WETH implementation.", + EnvVars: deployer.PrefixEnvVar("DELAYED_WETH_IMPL"), + Value: common.Address{}.Hex(), + } + AnchorStateRegistryProxyFlag = &cli.StringFlag{ + Name: AnchorStateRegistryProxyFlagName, + Usage: "Anchor state registry proxy.", + EnvVars: deployer.PrefixEnvVar("ANCHOR_STATE_REGISTRY_PROXY"), + } + L2ChainIdFlag = &cli.Uint64Flag{ + Name: L2ChainIdFlagName, + Usage: "L2 chain ID.", + EnvVars: deployer.PrefixEnvVar("L2_CHAIN_ID"), + } + ProposerFlag = &cli.StringFlag{ + Name: ProposerFlagName, + Usage: "Proposer address (permissioned game only).", + EnvVars: deployer.PrefixEnvVar("PROPOSER"), + Value: common.Address{}.Hex(), + } + ChallengerFlag = &cli.StringFlag{ + Name: ChallengerFlagName, + Usage: "Challenger address (permissioned game only).", + EnvVars: deployer.PrefixEnvVar("CHALLENGER"), + Value: common.Address{}.Hex(), + } + PreimageOracleFlag = &cli.StringFlag{ + Name: PreimageOracleFlagName, + Usage: "Preimage oracle address.", + EnvVars: deployer.PrefixEnvVar("PREIMAGE_ORACLE"), + Value: common.Address{}.Hex(), + } + ReleaseFlag = &cli.StringFlag{ + Name: ReleaseFlagName, + Usage: "Release to deploy.", + EnvVars: deployer.PrefixEnvVar("RELEASE"), + } +) + +var OPCMFlags = []cli.Flag{ + deployer.L1RPCURLFlag, + deployer.PrivateKeyFlag, + ReleaseFlag, +} + +var ImplementationsFlags = []cli.Flag{ + MIPSVersionFlag, + WithdrawalDelaySecondsFlag, + MinProposalSizeBytesFlag, + ChallengePeriodSecondsFlag, + ProofMaturityDelaySecondsFlag, + DisputeGameFinalityDelaySecondsFlag, +} + +var DelayedWETHFlags = []cli.Flag{ + deployer.L1RPCURLFlag, + deployer.PrivateKeyFlag, + ArtifactsLocatorFlag, + DelayedWethImplFlag, +} + +var DisputeGameFlags = []cli.Flag{ + deployer.L1RPCURLFlag, + deployer.PrivateKeyFlag, + ArtifactsLocatorFlag, + MinProposalSizeBytesFlag, + ChallengePeriodSecondsFlag, + VmFlag, + GameKindFlag, + GameTypeFlag, + AbsolutePrestateFlag, + MaxGameDepthFlag, + SplitDepthFlag, + ClockExtensionFlag, + MaxClockDurationFlag, + DelayedWethProxyFlag, + AnchorStateRegistryProxyFlag, + L2ChainIdFlag, + ProposerFlag, + ChallengerFlag, +} + +var BaseFPVMFlags = []cli.Flag{ + deployer.L1RPCURLFlag, + deployer.PrivateKeyFlag, + ArtifactsLocatorFlag, + PreimageOracleFlag, +} + +var MIPSFlags = append(BaseFPVMFlags, MIPSVersionFlag) + +var AsteriscFlags = BaseFPVMFlags + +var Commands = []*cli.Command{ + { + Name: "opcm", + Usage: "Bootstrap an instance of OPCM.", + Flags: cliapp.ProtectFlags(OPCMFlags), + Action: OPCMCLI, + }, + { + Name: "implementations", + Usage: "Bootstraps implementations.", + Flags: cliapp.ProtectFlags(ImplementationsFlags), + Action: func(context *cli.Context) error { + return errors.New("not implemented yet") + }, + Hidden: true, + }, + { + Name: "delayedweth", + Usage: "Bootstrap an instance of DelayedWETH.", + Flags: cliapp.ProtectFlags(DelayedWETHFlags), + Action: DelayedWETHCLI, + }, + { + Name: "disputegame", + Usage: "Bootstrap an instance of a FaultDisputeGame or PermissionedDisputeGame.", + Flags: cliapp.ProtectFlags(DisputeGameFlags), + Action: DisputeGameCLI, + }, + { + Name: "mips", + Usage: "Bootstrap an instance of MIPS.", + Flags: cliapp.ProtectFlags(MIPSFlags), + Action: MIPSCLI, + }, + { + Name: "asterisc", + Usage: "Bootstrap an instance of Asterisc.", + Flags: cliapp.ProtectFlags(AsteriscFlags), + Action: AsteriscCLI, + }, +} diff --git a/op-deployer/pkg/deployer/bootstrap/mips.go b/op-deployer/pkg/deployer/bootstrap/mips.go new file mode 100644 index 0000000000000..9c8fd555fb421 --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/mips.go @@ -0,0 +1,196 @@ +package bootstrap + +import ( + "context" + "crypto/ecdsa" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/rpc" + + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +type MIPSConfig struct { + L1RPCUrl string + PrivateKey string + Logger log.Logger + ArtifactsLocator *artifacts2.Locator + + privateKeyECDSA *ecdsa.PrivateKey + + PreimageOracle common.Address + MipsVersion uint64 +} + +func (c *MIPSConfig) Check() error { + if c.L1RPCUrl == "" { + return fmt.Errorf("l1RPCUrl must be specified") + } + + if c.PrivateKey == "" { + return fmt.Errorf("private key must be specified") + } + + privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x")) + if err != nil { + return fmt.Errorf("failed to parse private key: %w", err) + } + c.privateKeyECDSA = privECDSA + + if c.Logger == nil { + return fmt.Errorf("logger must be specified") + } + + if c.ArtifactsLocator == nil { + return fmt.Errorf("artifacts locator must be specified") + } + + if c.PreimageOracle == (common.Address{}) { + return fmt.Errorf("preimage oracle must be specified") + } + + if c.MipsVersion == 0 { + return fmt.Errorf("mips version must be specified") + } + if c.MipsVersion != 1 && c.MipsVersion != 2 { + return fmt.Errorf("mips version must be either 1 or 2") + } + + return nil +} + +func MIPSCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) + privateKey := cliCtx.String(deployer.PrivateKeyFlagName) + artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsLocator := new(artifacts2.Locator) + if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { + return fmt.Errorf("failed to parse artifacts URL: %w", err) + } + + mipsVersion := cliCtx.Uint64(MIPSVersionFlagName) + preimageOracle := common.HexToAddress(cliCtx.String(PreimageOracleFlagName)) + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + return MIPS(ctx, MIPSConfig{ + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + Logger: l, + ArtifactsLocator: artifactsLocator, + MipsVersion: mipsVersion, + PreimageOracle: preimageOracle, + }) +} + +func MIPS(ctx context.Context, cfg MIPSConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for MIPS: %w", err) + } + + lgr := cfg.Logger + progressor := func(curr, total int64) { + lgr.Info("artifacts download progress", "current", curr, "total", total) + } + + artifactsFS, cleanup, err := artifacts2.Download(ctx, cfg.ArtifactsLocator, progressor) + if err != nil { + return fmt.Errorf("failed to download artifacts: %w", err) + } + defer func() { + if err := cleanup(); err != nil { + lgr.Warn("failed to clean up artifacts", "err", err) + } + }() + + l1RPC, err := rpc.Dial(cfg.L1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + l1Client := ethclient.NewClient(l1RPC) + + chainID, err := l1Client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get chain ID: %w", err) + } + + signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID)) + chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey) + + bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ + Logger: lgr, + ChainID: chainID, + Client: l1Client, + Signer: signer, + From: chainDeployer, + }) + if err != nil { + return fmt.Errorf("failed to create broadcaster: %w", err) + } + + host, err := env.DefaultForkedScriptHost( + ctx, + bcaster, + lgr, + chainDeployer, + artifactsFS, + l1RPC, + ) + if err != nil { + return fmt.Errorf("failed to create script host: %w", err) + } + + var release string + if cfg.ArtifactsLocator.IsTag() { + release = cfg.ArtifactsLocator.Tag + } else { + release = "dev" + } + + lgr.Info("deploying dispute game", "release", release) + + dgo, err := opcm.DeployMIPS( + host, + opcm.DeployMIPSInput{ + MipsVersion: cfg.MipsVersion, + PreimageOracle: cfg.PreimageOracle, + }, + ) + if err != nil { + return fmt.Errorf("error deploying dispute game: %w", err) + } + + if _, err := bcaster.Broadcast(ctx); err != nil { + return fmt.Errorf("failed to broadcast: %w", err) + } + + lgr.Info("deployed dispute game") + + if err := jsonutil.WriteJSON(dgo, ioutil.ToStdOut()); err != nil { + return fmt.Errorf("failed to write output: %w", err) + } + return nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/opcm.go b/op-deployer/pkg/deployer/bootstrap/opcm.go new file mode 100644 index 0000000000000..5d42d1bfa9556 --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/opcm.go @@ -0,0 +1,229 @@ +package bootstrap + +import ( + "context" + "crypto/ecdsa" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +type OPCMConfig struct { + L1RPCUrl string + PrivateKey string + Release string + Logger log.Logger + + privateKeyECDSA *ecdsa.PrivateKey +} + +func (c *OPCMConfig) Check() error { + if c.L1RPCUrl == "" { + return fmt.Errorf("l1RPCUrl must be specified") + } + + if c.PrivateKey == "" { + return fmt.Errorf("private key must be specified") + } + + if c.Release == "" { + return fmt.Errorf("release must be specified") + } + + privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x")) + if err != nil { + return fmt.Errorf("failed to parse private key: %w", err) + } + c.privateKeyECDSA = privECDSA + + if c.Logger == nil { + return fmt.Errorf("logger must be specified") + } + + return nil +} + +func OPCMCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) + privateKey := cliCtx.String(deployer.PrivateKeyFlagName) + release := cliCtx.String(ReleaseFlagName) + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + out, err := OPCM(ctx, OPCMConfig{ + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + Release: release, + Logger: l, + }) + if err != nil { + return fmt.Errorf("failed to deploy OPCM: %w", err) + } + + if err := jsonutil.WriteJSON(out, ioutil.ToStdOut()); err != nil { + return fmt.Errorf("failed to write output: %w", err) + } + return nil +} + +func OPCM(ctx context.Context, cfg OPCMConfig) (opcm.DeployOPCMOutput, error) { + var out opcm.DeployOPCMOutput + if err := cfg.Check(); err != nil { + return out, fmt.Errorf("invalid config for OPCM: %w", err) + } + + lgr := cfg.Logger + progressor := func(curr, total int64) { + lgr.Info("artifacts download progress", "current", curr, "total", total) + } + + l1RPC, err := rpc.Dial(cfg.L1RPCUrl) + if err != nil { + return out, fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + + l1Client := ethclient.NewClient(l1RPC) + + chainID, err := l1Client.ChainID(ctx) + if err != nil { + return out, fmt.Errorf("failed to get chain ID: %w", err) + } + chainIDU64 := chainID.Uint64() + + loc, err := artifacts.NewLocatorFromTag(cfg.Release) + if err != nil { + return out, fmt.Errorf("failed to create artifacts locator: %w", err) + } + + artifactsFS, cleanup, err := artifacts.Download(ctx, loc, progressor) + if err != nil { + return out, fmt.Errorf("failed to download artifacts: %w", err) + } + defer func() { + if err := cleanup(); err != nil { + lgr.Warn("failed to clean up artifacts", "err", err) + } + }() + + signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID)) + chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey) + + bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{ + Logger: lgr, + ChainID: chainID, + Client: l1Client, + Signer: signer, + From: chainDeployer, + }) + if err != nil { + return out, fmt.Errorf("failed to create broadcaster: %w", err) + } + + host, err := env.DefaultForkedScriptHost( + ctx, + bcaster, + lgr, + chainDeployer, + artifactsFS, + l1RPC, + ) + if err != nil { + return out, fmt.Errorf("failed to create script host: %w", err) + } + + lgr.Info("deploying OPCM", "l1ContractsRelease", cfg.Release) + + input, err := DeployOPCMInputForChain(cfg.Release, chainIDU64) + if err != nil { + return out, fmt.Errorf("error creating OPCM input: %w", err) + } + + out, err = opcm.DeployOPCM( + host, + input, + ) + if err != nil { + return out, fmt.Errorf("error deploying implementations: %w", err) + } + + if _, err := bcaster.Broadcast(ctx); err != nil { + return out, fmt.Errorf("failed to broadcast: %w", err) + } + + lgr.Info("deployed OPCM") + + return out, nil +} + +func DeployOPCMInputForChain(release string, chainID uint64) (opcm.DeployOPCMInput, error) { + superchain, err := standard.SuperchainFor(chainID) + if err != nil { + return opcm.DeployOPCMInput{}, fmt.Errorf("error getting superchain config: %w", err) + } + + l1VersionsData, err := standard.L1VersionsFor(chainID) + if err != nil { + return opcm.DeployOPCMInput{}, fmt.Errorf("error getting L1 versions: %w", err) + } + releases, ok := l1VersionsData.Releases[release] + if !ok { + return opcm.DeployOPCMInput{}, fmt.Errorf("release not found: %s", release) + } + + blueprints, err := standard.OPCMBlueprintsFor(chainID) + if err != nil { + return opcm.DeployOPCMInput{}, fmt.Errorf("error getting OPCM blueprints: %w", err) + } + + return opcm.DeployOPCMInput{ + SuperchainConfig: common.Address(*superchain.Config.SuperchainConfigAddr), + ProtocolVersions: common.Address(*superchain.Config.ProtocolVersionsAddr), + L1ContractsRelease: strings.TrimPrefix(release, "op-contracts/"), + + AddressManagerBlueprint: blueprints.AddressManager, + ProxyBlueprint: blueprints.Proxy, + ProxyAdminBlueprint: blueprints.ProxyAdmin, + L1ChugSplashProxyBlueprint: blueprints.L1ChugSplashProxy, + ResolvedDelegateProxyBlueprint: blueprints.ResolvedDelegateProxy, + AnchorStateRegistryBlueprint: blueprints.AnchorStateRegistry, + PermissionedDisputeGame1Blueprint: blueprints.PermissionedDisputeGame1, + PermissionedDisputeGame2Blueprint: blueprints.PermissionedDisputeGame2, + + L1ERC721BridgeImpl: releases.L1ERC721Bridge.ImplementationAddress, + OptimismPortalImpl: releases.OptimismPortal.ImplementationAddress, + SystemConfigImpl: releases.SystemConfig.ImplementationAddress, + OptimismMintableERC20FactoryImpl: releases.OptimismMintableERC20Factory.ImplementationAddress, + L1CrossDomainMessengerImpl: releases.L1CrossDomainMessenger.ImplementationAddress, + L1StandardBridgeImpl: releases.L1StandardBridge.ImplementationAddress, + DisputeGameFactoryImpl: releases.DisputeGameFactory.ImplementationAddress, + DelayedWETHImpl: releases.DelayedWETH.ImplementationAddress, + MipsImpl: releases.MIPS.Address, + }, nil +} diff --git a/op-deployer/pkg/deployer/bootstrap/opcm_test.go b/op-deployer/pkg/deployer/bootstrap/opcm_test.go new file mode 100644 index 0000000000000..5a4d1e8de3d51 --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/opcm_test.go @@ -0,0 +1,82 @@ +package bootstrap + +import ( + "context" + "log/slog" + "os" + "strings" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/retryproxy" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils/anvil" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +var networks = []string{"mainnet", "sepolia"} + +var versions = []string{"v1.8.0-rc.3"} + +func TestOPCMLiveChain(t *testing.T) { + for _, network := range networks { + for _, version := range versions { + t.Run(network+"-"+version, func(t *testing.T) { + if version == "v1.8.0-rc.3" && network == "mainnet" { + t.Skip("v1.8.0-rc.3 not supported on mainnet yet") + } + + envVar := strings.ToUpper(network) + "_RPC_URL" + rpcURL := os.Getenv(envVar) + require.NotEmpty(t, rpcURL, "must specify RPC url via %s env var", envVar) + testOPCMLiveChain(t, "op-contracts/"+version, rpcURL) + }) + } + } +} + +func testOPCMLiveChain(t *testing.T, version string, forkRPCURL string) { + t.Parallel() + + if forkRPCURL == "" { + t.Skip("forkRPCURL not set") + } + + lgr := testlog.Logger(t, slog.LevelDebug) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + retryProxy := retryproxy.New(lgr, forkRPCURL) + require.NoError(t, retryProxy.Start()) + t.Cleanup(func() { + require.NoError(t, retryProxy.Stop()) + }) + + runner, err := anvil.New( + retryProxy.Endpoint(), + lgr, + ) + require.NoError(t, err) + + require.NoError(t, runner.Start(ctx)) + t.Cleanup(func() { + require.NoError(t, runner.Stop()) + }) + + out, err := OPCM(ctx, OPCMConfig{ + L1RPCUrl: runner.RPCUrl(), + PrivateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + Release: version, + Logger: lgr, + }) + require.NoError(t, err) + require.NotEmpty(t, out.Opcm) + + client, err := ethclient.Dial(runner.RPCUrl()) + require.NoError(t, err) + code, err := client.CodeAt(ctx, out.Opcm, nil) + require.NoError(t, err) + require.NotEmpty(t, code) +} diff --git a/op-chain-ops/deployer/broadcaster/broadcaster.go b/op-deployer/pkg/deployer/broadcaster/broadcaster.go similarity index 100% rename from op-chain-ops/deployer/broadcaster/broadcaster.go rename to op-deployer/pkg/deployer/broadcaster/broadcaster.go diff --git a/op-chain-ops/deployer/broadcaster/discard.go b/op-deployer/pkg/deployer/broadcaster/discard.go similarity index 89% rename from op-chain-ops/deployer/broadcaster/discard.go rename to op-deployer/pkg/deployer/broadcaster/discard.go index 42f5b1b0a9642..a284db61a2139 100644 --- a/op-chain-ops/deployer/broadcaster/discard.go +++ b/op-deployer/pkg/deployer/broadcaster/discard.go @@ -9,7 +9,7 @@ import ( type discardBroadcaster struct { } -func DiscardBroadcaster() Broadcaster { +func NoopBroadcaster() Broadcaster { return &discardBroadcaster{} } diff --git a/op-chain-ops/deployer/broadcaster/gas_estimator.go b/op-deployer/pkg/deployer/broadcaster/gas_estimator.go similarity index 72% rename from op-chain-ops/deployer/broadcaster/gas_estimator.go rename to op-deployer/pkg/deployer/broadcaster/gas_estimator.go index abe76d027ec40..b04390fc8aa78 100644 --- a/op-chain-ops/deployer/broadcaster/gas_estimator.go +++ b/op-deployer/pkg/deployer/broadcaster/gas_estimator.go @@ -11,15 +11,20 @@ import ( var ( // baseFeePadFactor = 50% as a divisor baseFeePadFactor = big.NewInt(2) - // tipMulFactor = 20 as a multiplier - tipMulFactor = big.NewInt(20) + // tipMulFactor = 5 as a multiplier + tipMulFactor = big.NewInt(5) // dummyBlobFee is a dummy value for the blob fee. Since this gas estimator will never // post blobs, it's just set to 1. dummyBlobFee = big.NewInt(1) + // maxTip is the maximum tip that can be suggested by this estimator. + maxTip = big.NewInt(50 * 1e9) + // minTip is the minimum tip that can be suggested by this estimator. + minTip = big.NewInt(1 * 1e9) ) // DeployerGasPriceEstimator is a custom gas price estimator for use with op-deployer. -// It pads the base fee by 50% and multiplies the suggested tip by 20. +// It pads the base fee by 50% and multiplies the suggested tip by 5 up to a max of +// 50 gwei. func DeployerGasPriceEstimator(ctx context.Context, client txmgr.ETHBackend) (*big.Int, *big.Int, *big.Int, error) { chainHead, err := client.HeaderByNumber(ctx, nil) if err != nil { @@ -34,5 +39,14 @@ func DeployerGasPriceEstimator(ctx context.Context, client txmgr.ETHBackend) (*b baseFeePad := new(big.Int).Div(chainHead.BaseFee, baseFeePadFactor) paddedBaseFee := new(big.Int).Add(chainHead.BaseFee, baseFeePad) paddedTip := new(big.Int).Mul(tip, tipMulFactor) + + if paddedTip.Cmp(minTip) < 0 { + paddedTip.Set(minTip) + } + + if paddedTip.Cmp(maxTip) > 0 { + paddedTip.Set(maxTip) + } + return paddedTip, paddedBaseFee, dummyBlobFee, nil } diff --git a/op-chain-ops/deployer/broadcaster/keyed.go b/op-deployer/pkg/deployer/broadcaster/keyed.go similarity index 89% rename from op-chain-ops/deployer/broadcaster/keyed.go rename to op-deployer/pkg/deployer/broadcaster/keyed.go index 8926c02943b67..c9bb27fcf0cec 100644 --- a/op-chain-ops/deployer/broadcaster/keyed.go +++ b/op-deployer/pkg/deployer/broadcaster/keyed.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "sync" "time" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -29,6 +30,7 @@ type KeyedBroadcaster struct { mgr txmgr.TxManager bcasts []script.Broadcast client *ethclient.Client + mtx sync.Mutex } type KeyedBroadcasterOpts struct { @@ -88,20 +90,35 @@ func NewKeyedBroadcaster(cfg KeyedBroadcasterOpts) (*KeyedBroadcaster, error) { } func (t *KeyedBroadcaster) Hook(bcast script.Broadcast) { + if bcast.Type != script.BroadcastCreate2 && bcast.From != t.mgr.From() { + panic(fmt.Sprintf("invalid from for broadcast:%v, expected:%v", bcast.From, t.mgr.From())) + } + t.mtx.Lock() t.bcasts = append(t.bcasts, bcast) + t.mtx.Unlock() } func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, error) { - results := make([]BroadcastResult, len(t.bcasts)) - futures := make([]<-chan txmgr.SendResponse, len(t.bcasts)) - ids := make([]common.Hash, len(t.bcasts)) + // Empty the internal broadcast buffer as soon as this method is called. + t.mtx.Lock() + bcasts := t.bcasts + t.bcasts = nil + t.mtx.Unlock() + + if len(bcasts) == 0 { + return nil, nil + } + + results := make([]BroadcastResult, len(bcasts)) + futures := make([]<-chan txmgr.SendResponse, len(bcasts)) + ids := make([]common.Hash, len(bcasts)) latestBlock, err := t.client.BlockByNumber(ctx, nil) if err != nil { return nil, fmt.Errorf("failed to get latest block: %w", err) } - for i, bcast := range t.bcasts { + for i, bcast := range bcasts { futures[i], ids[i] = t.broadcast(ctx, bcast, latestBlock.GasLimit()) t.lgr.Info( "transaction broadcasted", @@ -116,7 +133,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er bcastRes := <-fut completed++ outRes := BroadcastResult{ - Broadcast: t.bcasts[i], + Broadcast: bcasts[i], } if bcastRes.Err == nil { @@ -131,7 +148,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er "transaction failed on chain", "id", ids[i], "completed", completed, - "total", len(t.bcasts), + "total", len(bcasts), "hash", outRes.Receipt.TxHash.String(), "nonce", outRes.Broadcast.Nonce, ) @@ -140,7 +157,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er "transaction confirmed", "id", ids[i], "completed", completed, - "total", len(t.bcasts), + "total", len(bcasts), "hash", outRes.Receipt.TxHash.String(), "nonce", outRes.Broadcast.Nonce, "creation", outRes.Receipt.ContractAddress, @@ -153,7 +170,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er "transaction failed", "id", ids[i], "completed", completed, - "total", len(t.bcasts), + "total", len(bcasts), "err", bcastRes.Err, ) } diff --git a/op-chain-ops/deployer/flags.go b/op-deployer/pkg/deployer/flags.go similarity index 50% rename from op-chain-ops/deployer/flags.go rename to op-deployer/pkg/deployer/flags.go index c0f2ba92f14bb..a58d35bbbb8d1 100644 --- a/op-chain-ops/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -1,28 +1,33 @@ package deployer import ( + "fmt" "os" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + op_service "github.com/ethereum-optimism/optimism/op-service" oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/urfave/cli/v2" ) const ( - EnvVarPrefix = "DEPLOYER" - L1RPCURLFlagName = "l1-rpc-url" - L1ChainIDFlagName = "l1-chain-id" - L2ChainIDsFlagName = "l2-chain-ids" - WorkdirFlagName = "workdir" - OutdirFlagName = "outdir" - PrivateKeyFlagName = "private-key" + EnvVarPrefix = "DEPLOYER" + L1RPCURLFlagName = "l1-rpc-url" + L1ChainIDFlagName = "l1-chain-id" + L2ChainIDsFlagName = "l2-chain-ids" + WorkdirFlagName = "workdir" + OutdirFlagName = "outdir" + PrivateKeyFlagName = "private-key" + DeploymentStrategyFlagName = "deployment-strategy" + IntentConfigTypeFlagName = "intent-config-type" ) var ( L1RPCURLFlag = &cli.StringFlag{ Name: L1RPCURLFlagName, - Usage: "RPC URL for the L1 chain. Can be set to 'genesis' for deployments " + - "that will be deployed at the launch of the L1.", + Usage: "RPC URL for the L1 chain. Must be set for live chains. " + + "Can be blank for chains deploying to local allocs files.", EnvVars: []string{ "L1_RPC_URL", }, @@ -31,7 +36,7 @@ var ( Name: L1ChainIDFlagName, Usage: "Chain ID of the L1 chain.", EnvVars: PrefixEnvVar("L1_CHAIN_ID"), - Value: 900, + Value: 11155111, } L2ChainIDsFlag = &cli.StringFlag{ Name: L2ChainIDsFlagName, @@ -52,6 +57,23 @@ var ( Usage: "Private key of the deployer account.", EnvVars: PrefixEnvVar("PRIVATE_KEY"), } + DeploymentStrategyFlag = &cli.StringFlag{ + Name: DeploymentStrategyFlagName, + Usage: fmt.Sprintf("Deployment strategy to use. Options: %s, %s", state.DeploymentStrategyLive, state.DeploymentStrategyGenesis), + EnvVars: PrefixEnvVar("DEPLOYMENT_STRATEGY"), + Value: string(state.DeploymentStrategyLive), + } + IntentConfigTypeFlag = &cli.StringFlag{ + Name: IntentConfigTypeFlagName, + Usage: fmt.Sprintf("Intent config type to use. Options: %s (default), %s, %s, %s, %s", + state.IntentConfigTypeStandard, + state.IntentConfigTypeCustom, + state.IntentConfigTypeStrict, + state.IntentConfigTypeStandardOverrides, + state.IntentConfigTypeStrictOverrides), + EnvVars: PrefixEnvVar("INTENT_CONFIG_TYPE"), + Value: string(state.IntentConfigTypeStandard), + } ) var GlobalFlags = append([]cli.Flag{}, oplog.CLIFlags(EnvVarPrefix)...) @@ -60,6 +82,8 @@ var InitFlags = []cli.Flag{ L1ChainIDFlag, L2ChainIDsFlag, WorkdirFlag, + DeploymentStrategyFlag, + IntentConfigTypeFlag, } var ApplyFlags = []cli.Flag{ diff --git a/op-deployer/pkg/deployer/init.go b/op-deployer/pkg/deployer/init.go new file mode 100644 index 0000000000000..deca136558cbc --- /dev/null +++ b/op-deployer/pkg/deployer/init.go @@ -0,0 +1,118 @@ +package deployer + +import ( + "errors" + "fmt" + "os" + "path" + "strings" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + + op_service "github.com/ethereum-optimism/optimism/op-service" + + "github.com/ethereum/go-ethereum/common" + "github.com/urfave/cli/v2" +) + +type InitConfig struct { + DeploymentStrategy state.DeploymentStrategy + IntentConfigType state.IntentConfigType + L1ChainID uint64 + Outdir string + L2ChainIDs []common.Hash +} + +func (c *InitConfig) Check() error { + if err := c.DeploymentStrategy.Check(); err != nil { + return err + } + + if c.L1ChainID == 0 { + return fmt.Errorf("l1ChainID must be specified") + } + + if c.Outdir == "" { + return fmt.Errorf("outdir must be specified") + } + + if len(c.L2ChainIDs) == 0 { + return fmt.Errorf("must specify at least one L2 chain ID") + } + + return nil +} + +func InitCLI() func(ctx *cli.Context) error { + return func(ctx *cli.Context) error { + deploymentStrategy := ctx.String(DeploymentStrategyFlagName) + l1ChainID := ctx.Uint64(L1ChainIDFlagName) + outdir := ctx.String(OutdirFlagName) + l2ChainIDsRaw := ctx.String(L2ChainIDsFlagName) + intentConfigType := ctx.String(IntentConfigTypeFlagName) + + if len(l2ChainIDsRaw) == 0 { + return fmt.Errorf("must specify at least one L2 chain ID") + } + + l2ChainIDsStr := strings.Split(strings.TrimSpace(l2ChainIDsRaw), ",") + l2ChainIDs := make([]common.Hash, len(l2ChainIDsStr)) + for i, idStr := range l2ChainIDsStr { + id, err := op_service.Parse256BitChainID(idStr) + if err != nil { + return fmt.Errorf("invalid L2 chain ID '%s': %w", idStr, err) + } + l2ChainIDs[i] = id + } + + err := Init(InitConfig{ + DeploymentStrategy: state.DeploymentStrategy(deploymentStrategy), + IntentConfigType: state.IntentConfigType(intentConfigType), + L1ChainID: l1ChainID, + Outdir: outdir, + L2ChainIDs: l2ChainIDs, + }) + if err != nil { + return err + } + + fmt.Printf("Successfully initialized op-deployer intent in directory: %s\n", outdir) + return nil + } +} + +func Init(cfg InitConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for init: %w", err) + } + + intent, err := state.NewIntent(cfg.IntentConfigType, cfg.DeploymentStrategy, cfg.L1ChainID, cfg.L2ChainIDs) + if err != nil { + return err + } + intent.DeploymentStrategy = cfg.DeploymentStrategy + intent.ConfigType = cfg.IntentConfigType + + st := &state.State{ + Version: 1, + } + + stat, err := os.Stat(cfg.Outdir) + if errors.Is(err, os.ErrNotExist) { + if err := os.MkdirAll(cfg.Outdir, 0755); err != nil { + return fmt.Errorf("failed to create outdir: %w", err) + } + } else if err != nil { + return fmt.Errorf("failed to stat outdir: %w", err) + } else if !stat.IsDir() { + return fmt.Errorf("outdir is not a directory") + } + + if err := intent.WriteToFile(path.Join(cfg.Outdir, "intent.toml")); err != nil { + return fmt.Errorf("failed to write intent to file: %w", err) + } + if err := st.WriteToFile(path.Join(cfg.Outdir, "state.json")); err != nil { + return fmt.Errorf("failed to write state to file: %w", err) + } + return nil +} diff --git a/op-deployer/pkg/deployer/inspect/deploy_config.go b/op-deployer/pkg/deployer/inspect/deploy_config.go new file mode 100644 index 0000000000000..575576633ab71 --- /dev/null +++ b/op-deployer/pkg/deployer/inspect/deploy_config.go @@ -0,0 +1,59 @@ +package inspect + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + "github.com/ethereum/go-ethereum/common" + "github.com/urfave/cli/v2" +) + +func DeployConfigCLI(cliCtx *cli.Context) error { + cliCfg, err := readConfig(cliCtx) + if err != nil { + return err + } + + globalState, err := pipeline.ReadState(cliCfg.Workdir) + if err != nil { + return fmt.Errorf("failed to read globalState: %w", err) + } + + config, err := DeployConfig(globalState, cliCfg.ChainID) + if err != nil { + return fmt.Errorf("failed to generate deploy config: %w", err) + } + + if err := jsonutil.WriteJSON(config, ioutil.ToStdOutOrFileOrNoop(cliCfg.Outfile, 0o666)); err != nil { + return fmt.Errorf("failed to write deploy config: %w", err) + } + + return nil +} + +func DeployConfig(globalState *state.State, chainID common.Hash) (*genesis.DeployConfig, error) { + chainState, err := globalState.Chain(chainID) + if err != nil { + return nil, fmt.Errorf("failed to find chain state: %w", err) + } + + intent := globalState.AppliedIntent + if intent == nil { + return nil, fmt.Errorf("can only run this command following a full apply") + } + chainIntent, err := intent.Chain(chainID) + if err != nil { + return nil, fmt.Errorf("failed to find chain intent: %w", err) + } + + config, err := state.CombineDeployConfig(intent, chainIntent, globalState, chainState) + if err != nil { + return nil, fmt.Errorf("failed to generate deploy config: %w", err) + } + + return &config, nil +} diff --git a/op-chain-ops/deployer/inspect/flags.go b/op-deployer/pkg/deployer/inspect/flags.go similarity index 72% rename from op-chain-ops/deployer/inspect/flags.go rename to op-deployer/pkg/deployer/inspect/flags.go index b84bb4c80323a..3f74e1d759b2f 100644 --- a/op-chain-ops/deployer/inspect/flags.go +++ b/op-deployer/pkg/deployer/inspect/flags.go @@ -3,9 +3,10 @@ package inspect import ( "fmt" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + op_service "github.com/ethereum-optimism/optimism/op-service" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" "github.com/ethereum/go-ethereum/common" "github.com/urfave/cli/v2" ) @@ -52,6 +53,30 @@ var Commands = []*cli.Command{ Action: RollupCLI, Flags: Flags, }, + { + Name: "deploy-config", + Usage: "outputs the deploy config for an L2 chain", + Args: true, + ArgsUsage: "", + Action: DeployConfigCLI, + Flags: Flags, + }, + { + Name: "l2-semvers", + Usage: "outputs the semvers for all L2 chains", + Args: true, + ArgsUsage: "", + Action: L2SemversCLI, + Flags: Flags, + }, + { + Name: "superchain-registry", + Usage: "outputs the .env file expected by superchain-registry add-chain tool", + Args: true, + ArgsUsage: "", + Action: SuperchainRegistryCLI, + Flags: Flags, + }, } type cliConfig struct { diff --git a/op-chain-ops/deployer/inspect/genesis.go b/op-deployer/pkg/deployer/inspect/genesis.go similarity index 86% rename from op-chain-ops/deployer/inspect/genesis.go rename to op-deployer/pkg/deployer/inspect/genesis.go index 4c2fbad010929..ff6fe462c90ad 100644 --- a/op-chain-ops/deployer/inspect/genesis.go +++ b/op-deployer/pkg/deployer/inspect/genesis.go @@ -3,8 +3,9 @@ package inspect import ( "fmt" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum/go-ethereum/core" @@ -21,8 +22,7 @@ func GenesisCLI(cliCtx *cli.Context) error { return err } - env := &pipeline.Env{Workdir: cfg.Workdir} - globalState, err := env.ReadState() + globalState, err := pipeline.ReadState(cfg.Workdir) if err != nil { return fmt.Errorf("failed to read intent: %w", err) } @@ -54,11 +54,7 @@ func GenesisAndRollup(globalState *state.State, chainID common.Hash) (*core.Gene return nil, nil, fmt.Errorf("failed to get chain ID %s: %w", chainID.String(), err) } - l2Allocs, err := chainState.UnmarshalAllocs() - if err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal genesis: %w", err) - } - + l2Allocs := chainState.Allocs.Data config, err := state.CombineDeployConfig( globalState.AppliedIntent, chainIntent, diff --git a/op-chain-ops/deployer/inspect/l1.go b/op-deployer/pkg/deployer/inspect/l1.go similarity index 82% rename from op-chain-ops/deployer/inspect/l1.go rename to op-deployer/pkg/deployer/inspect/l1.go index 423b326d19159..4883e83486c35 100644 --- a/op-chain-ops/deployer/inspect/l1.go +++ b/op-deployer/pkg/deployer/inspect/l1.go @@ -3,7 +3,9 @@ package inspect import ( "fmt" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/ethereum/go-ethereum/common" @@ -36,14 +38,14 @@ type OpChainDeployment struct { DisputeGameFactoryProxyAddress common.Address `json:"disputeGameFactoryProxyAddress"` AnchorStateRegistryProxyAddress common.Address `json:"anchorStateRegistryProxyAddress"` AnchorStateRegistryImplAddress common.Address `json:"anchorStateRegistryImplAddress"` - // FaultDisputeGameAddress common.Address `json:"faultDisputeGameAddress"` - PermissionedDisputeGameAddress common.Address `json:"permissionedDisputeGameAddress"` - DelayedWETHPermissionedGameProxyAddress common.Address `json:"delayedWETHPermissionedGameProxyAddress"` + FaultDisputeGameAddress common.Address `json:"faultDisputeGameAddress"` + PermissionedDisputeGameAddress common.Address `json:"permissionedDisputeGameAddress"` + DelayedWETHPermissionedGameProxyAddress common.Address `json:"delayedWETHPermissionedGameProxyAddress"` // DelayedWETHPermissionlessGameProxyAddress common.Address `json:"delayedWETHPermissionlessGameProxyAddress"` } type ImplementationsDeployment struct { - OpcmProxyAddress common.Address `json:"opcmProxyAddress"` + OpcmAddress common.Address `json:"opcmAddress"` DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` @@ -62,15 +64,27 @@ func L1CLI(cliCtx *cli.Context) error { return err } - env := &pipeline.Env{Workdir: cfg.Workdir} - globalState, err := env.ReadState() + globalState, err := pipeline.ReadState(cfg.Workdir) if err != nil { return fmt.Errorf("failed to read intent: %w", err) } - chainState, err := globalState.Chain(cfg.ChainID) + l1Contracts, err := L1(globalState, cfg.ChainID) + if err != nil { + return fmt.Errorf("failed to generate l1Contracts: %w", err) + } + + if err := jsonutil.WriteJSON(l1Contracts, ioutil.ToStdOutOrFileOrNoop(cfg.Outfile, 0o666)); err != nil { + return fmt.Errorf("failed to write L1 contract addresses: %w", err) + } + + return nil +} + +func L1(globalState *state.State, chainID common.Hash) (*L1Contracts, error) { + chainState, err := globalState.Chain(chainID) if err != nil { - return fmt.Errorf("failed to get chain state for ID %s: %w", cfg.ChainID.String(), err) + return nil, fmt.Errorf("failed to get chain state for ID %s: %w", chainID.String(), err) } l1Contracts := L1Contracts{ @@ -93,13 +107,13 @@ func L1CLI(cliCtx *cli.Context) error { DisputeGameFactoryProxyAddress: chainState.DisputeGameFactoryProxyAddress, AnchorStateRegistryProxyAddress: chainState.AnchorStateRegistryProxyAddress, AnchorStateRegistryImplAddress: chainState.AnchorStateRegistryImplAddress, - // FaultDisputeGameAddress: chainState.FaultDisputeGameAddress, - PermissionedDisputeGameAddress: chainState.PermissionedDisputeGameAddress, - DelayedWETHPermissionedGameProxyAddress: chainState.DelayedWETHPermissionedGameProxyAddress, + FaultDisputeGameAddress: chainState.FaultDisputeGameAddress, + PermissionedDisputeGameAddress: chainState.PermissionedDisputeGameAddress, + DelayedWETHPermissionedGameProxyAddress: chainState.DelayedWETHPermissionedGameProxyAddress, // DelayedWETHPermissionlessGameProxyAddress: chainState.DelayedWETHPermissionlessGameProxyAddress, }, ImplementationsDeployment: ImplementationsDeployment{ - OpcmProxyAddress: globalState.ImplementationsDeployment.OpcmProxyAddress, + OpcmAddress: globalState.ImplementationsDeployment.OpcmAddress, DelayedWETHImplAddress: globalState.ImplementationsDeployment.DelayedWETHImplAddress, OptimismPortalImplAddress: globalState.ImplementationsDeployment.OptimismPortalImplAddress, PreimageOracleSingletonAddress: globalState.ImplementationsDeployment.PreimageOracleSingletonAddress, @@ -113,9 +127,5 @@ func L1CLI(cliCtx *cli.Context) error { }, } - if err := jsonutil.WriteJSON(l1Contracts, ioutil.ToStdOutOrFileOrNoop(cfg.Outfile, 0o666)); err != nil { - return fmt.Errorf("failed to write L1 contract addresses: %w", err) - } - - return nil + return &l1Contracts, nil } diff --git a/op-chain-ops/deployer/inspect/rollup.go b/op-deployer/pkg/deployer/inspect/rollup.go similarity index 75% rename from op-chain-ops/deployer/inspect/rollup.go rename to op-deployer/pkg/deployer/inspect/rollup.go index 60cbf4f5c46c5..d094427d951f7 100644 --- a/op-chain-ops/deployer/inspect/rollup.go +++ b/op-deployer/pkg/deployer/inspect/rollup.go @@ -3,7 +3,8 @@ package inspect import ( "fmt" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/urfave/cli/v2" @@ -15,13 +16,15 @@ func RollupCLI(cliCtx *cli.Context) error { return err } - env := &pipeline.Env{Workdir: cfg.Workdir} - globalState, err := env.ReadState() + globalState, err := pipeline.ReadState(cfg.Workdir) if err != nil { return fmt.Errorf("failed to read intent: %w", err) } _, rollupConfig, err := GenesisAndRollup(globalState, cfg.ChainID) + if rollupConfig.HoloceneTime == nil { + rollupConfig.Genesis.SystemConfig.MarshalPreHolocene = true + } if err != nil { return fmt.Errorf("failed to generate rollup config: %w", err) } diff --git a/op-deployer/pkg/deployer/inspect/semvers.go b/op-deployer/pkg/deployer/inspect/semvers.go new file mode 100644 index 0000000000000..48e16d21dbc43 --- /dev/null +++ b/op-deployer/pkg/deployer/inspect/semvers.go @@ -0,0 +1,261 @@ +package inspect + +import ( + "bytes" + "context" + "fmt" + "math/big" + "regexp" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" + "github.com/urfave/cli/v2" +) + +func L2SemversCLI(cliCtx *cli.Context) error { + cliCfg, err := readConfig(cliCtx) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(cliCtx.Context, time.Minute) + defer cancel() + + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + globalState, err := pipeline.ReadState(cliCfg.Workdir) + if err != nil { + return fmt.Errorf("failed to read intent: %w", err) + } + chainState, err := globalState.Chain(cliCfg.ChainID) + if err != nil { + return fmt.Errorf("failed to find chain state: %w", err) + } + + intent := globalState.AppliedIntent + if intent == nil { + return fmt.Errorf("can only run this command following a full apply") + } + if chainState.Allocs == nil { + return fmt.Errorf("chain state does not have allocs") + } + + artifactsFS, cleanup, err := artifacts.Download(ctx, intent.L2ContractsLocator, artifacts.LogProgressor(l)) + if err != nil { + return fmt.Errorf("failed to download L2 artifacts: %w", err) + } + defer func() { + if err := cleanup(); err != nil { + l.Warn("failed to clean up L2 artifacts", "err", err) + } + }() + + ps, err := L2Semvers(L2SemversConfig{ + Lgr: l, + Artifacts: artifactsFS, + ChainState: chainState, + }) + if err != nil { + return fmt.Errorf("failed to get L2 semvers: %w", err) + } + + if err := jsonutil.WriteJSON(ps, ioutil.ToStdOutOrFileOrNoop(cliCfg.Outfile, 0o666)); err != nil { + return fmt.Errorf("failed to write rollup config: %w", err) + } + + return nil +} + +type L2SemversConfig struct { + Lgr log.Logger + Artifacts foundry.StatDirFs + ChainState *state.ChainState +} + +type L2PredeploySemvers struct { + L2ToL1MessagePasser string + DeployerWhitelist string + WETH string + L2CrossDomainMessenger string + L2StandardBridge string + SequencerFeeVault string + OptimismMintableERC20Factory string + L1BlockNumber string + GasPriceOracle string + L1Block string + LegacyMessagePasser string + L2ERC721Bridge string + OptimismMintableERC721Factory string + BaseFeeVault string + L1FeeVault string + SchemaRegistry string + EAS string + CrossL2Inbox string + L2toL2CrossDomainMessenger string + SuperchainWETH string + ETHLiquidity string + SuperchainTokenBridge string + OptimismMintableERC20 string + OptimismMintableERC721 string +} + +func L2Semvers(cfg L2SemversConfig) (*L2PredeploySemvers, error) { + l := cfg.Lgr + artifactsFS := cfg.Artifacts + chainState := cfg.ChainState + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + l, + common.Address{19: 0x01}, + artifactsFS, + ) + if err != nil { + return nil, fmt.Errorf("failed to create script host: %w", err) + } + host.ImportState(chainState.Allocs.Data) + + type contractToCheck struct { + Address common.Address + FieldPtr *string + Name string + } + + var ps L2PredeploySemvers + + contracts := []contractToCheck{ + {predeploys.L2ToL1MessagePasserAddr, &ps.L2ToL1MessagePasser, "L2ToL1MessagePasser"}, + {predeploys.DeployerWhitelistAddr, &ps.DeployerWhitelist, "DeployerWhitelist"}, + {predeploys.WETHAddr, &ps.WETH, "WETH"}, + {predeploys.L2CrossDomainMessengerAddr, &ps.L2CrossDomainMessenger, "L2CrossDomainMessenger"}, + {predeploys.L2StandardBridgeAddr, &ps.L2StandardBridge, "L2StandardBridge"}, + {predeploys.SequencerFeeVaultAddr, &ps.SequencerFeeVault, "SequencerFeeVault"}, + {predeploys.OptimismMintableERC20FactoryAddr, &ps.OptimismMintableERC20Factory, "OptimismMintableERC20Factory"}, + {predeploys.L1BlockNumberAddr, &ps.L1BlockNumber, "L1BlockNumber"}, + {predeploys.GasPriceOracleAddr, &ps.GasPriceOracle, "GasPriceOracle"}, + {predeploys.L1BlockAddr, &ps.L1Block, "L1Block"}, + {predeploys.LegacyMessagePasserAddr, &ps.LegacyMessagePasser, "LegacyMessagePasser"}, + {predeploys.L2ERC721BridgeAddr, &ps.L2ERC721Bridge, "L2ERC721Bridge"}, + {predeploys.OptimismMintableERC721FactoryAddr, &ps.OptimismMintableERC721Factory, "OptimismMintableERC721Factory"}, + {predeploys.BaseFeeVaultAddr, &ps.BaseFeeVault, "BaseFeeVault"}, + {predeploys.L1FeeVaultAddr, &ps.L1FeeVault, "L1FeeVault"}, + {predeploys.SchemaRegistryAddr, &ps.SchemaRegistry, "SchemaRegistry"}, + {predeploys.EASAddr, &ps.EAS, "EAS"}, + } + for _, contract := range contracts { + semver, err := ReadSemver(host, contract.Address) + if err != nil { + return nil, fmt.Errorf("failed to read semver for %s: %w", contract.Name, err) + } + + *contract.FieldPtr = semver + } + + erc20Semver, err := findSemverBytecode(host, predeploys.OptimismMintableERC20FactoryAddr) + if err == nil { + ps.OptimismMintableERC20 = erc20Semver + } else { + l.Warn("failed to find semver for OptimismMintableERC20", "err", err) + } + + erc721Semver, err := findSemverBytecode(host, predeploys.OptimismMintableERC721FactoryAddr) + if err == nil { + ps.OptimismMintableERC721 = erc721Semver + } else { + l.Warn("failed to find semver for OptimismMintableERC721", "err", err) + } + + return &ps, nil +} + +var versionSelector = []byte{0x54, 0xfd, 0x4d, 0x50} + +func ReadSemver(host *script.Host, addr common.Address) (string, error) { + data, _, err := host.Call( + common.Address{19: 0x01}, + addr, + bytes.Clone(versionSelector), + 1_000_000_000, + uint256.NewInt(0), + ) + if err != nil { + return "", fmt.Errorf("failed to call version on %s: %w", addr, err) + } + + // The second 32 bytes contain the length of the string + length := new(big.Int).SetBytes(data[32:64]).Int64() + // Start of the string data (after offset and length) + stringStart := 64 + stringEnd := int64(stringStart) + length + + // Bounds check + if stringEnd > int64(len(data)) { + return "", fmt.Errorf("string data out of bounds") + } + + return string(data[stringStart:stringEnd]), nil +} + +const patternLen = 24 + +var semverRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+([\w.+\-]*))\x00`) +var codeAddr = common.HexToAddress("0xc0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30000") + +func findSemverBytecode(host *script.Host, proxyAddr common.Address) (string, error) { + var implAddr common.Address + copy(implAddr[:], codeAddr[:]) + copy(implAddr[18:], proxyAddr[18:]) + + bytecode := host.GetCode(implAddr) + if len(bytecode) == 0 { + return "", fmt.Errorf("failed to get bytecode for factory") + } + + versionSelectorIndex := bytes.LastIndex(bytecode, versionSelector) + if versionSelectorIndex == -1 { + return "", fmt.Errorf("failed to find semver selector in factory bytecode") + } + + for i := versionSelectorIndex; i < len(bytecode); i++ { + if bytecode[i] == 0 { + continue + } + + if i+patternLen > len(bytecode) { + break + } + + slice := bytecode[i : i+patternLen] + if slice[0] == 0x00 { + continue + } + + matches := semverRegexp.FindSubmatch(slice) + if len(matches) == 0 { + continue + } + + return string(matches[1]), nil + } + + return "", fmt.Errorf("failed to find semver in factory bytecode") +} diff --git a/op-deployer/pkg/deployer/inspect/superchain_registry.go b/op-deployer/pkg/deployer/inspect/superchain_registry.go new file mode 100644 index 0000000000000..c8d57907d9fda --- /dev/null +++ b/op-deployer/pkg/deployer/inspect/superchain_registry.go @@ -0,0 +1,177 @@ +package inspect + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/superchain-registry/superchain" + + "github.com/urfave/cli/v2" +) + +func SuperchainRegistryCLI(cliCtx *cli.Context) error { + cfg, err := readConfig(cliCtx) + if err != nil { + return err + } + + globalIntent, err := pipeline.ReadIntent(cfg.Workdir) + if err != nil { + return fmt.Errorf("failed to read intent: %w", err) + } + + if err := globalIntent.Check(); err != nil { + return fmt.Errorf("intent check failed: %w", err) + } + + envVars := map[string]string{} + envVars["SCR_CHAIN_NAME"] = "" + envVars["SCR_CHAIN_SHORT_NAME"] = "" + envVars["SCR_PUBLIC_RPC"] = "" + envVars["SCR_SEQUENCER_RPC"] = "" + envVars["SCR_EXPLORER"] = "" + envVars["SCR_STANDARD_CHAIN_CANDIDATE"] = "false" + + creationCommit, err := standard.CommitForDeployTag(globalIntent.L2ContractsLocator.Tag) + if err != nil { + return fmt.Errorf("failed to get commit for deploy tag: %w", err) + } + envVars["SCR_GENESIS_CREATION_COMMIT"] = creationCommit + + l1ChainName, err := standard.ChainNameFor(globalIntent.L1ChainID) + if err != nil { + return fmt.Errorf("failed to get l1 chain name: %w", err) + } + envVars["SCR_SUPERCHAIN_TARGET"] = l1ChainName + + globalState, err := pipeline.ReadState(cfg.Workdir) + if err != nil { + return fmt.Errorf("failed to read state: %w", err) + } + + genesis, rollup, err := GenesisAndRollup(globalState, cfg.ChainID) + if err != nil { + return fmt.Errorf("failed to generate genesis and rollup: %w", err) + } + genesisFilepath := filepath.Join(cfg.Workdir, "genesis.json") + if err := jsonutil.WriteJSON(genesis, ioutil.ToStdOutOrFileOrNoop(genesisFilepath, 0o666)); err != nil { + return fmt.Errorf("failed to write genesis: %w", err) + } + rollupFilepath := filepath.Join(cfg.Workdir, "rollup.json") + if err := jsonutil.WriteJSON(rollup, ioutil.ToStdOutOrFileOrNoop(rollupFilepath, 0o666)); err != nil { + return fmt.Errorf("failed to write rollup: %w", err) + } + + deployConfig, err := DeployConfig(globalState, cfg.ChainID) + if err != nil { + return fmt.Errorf("failed to generate deploy config: %w", err) + } + deployConfigFilepath := filepath.Join(cfg.Workdir, "deploy-config.json") + if err := jsonutil.WriteJSON(deployConfig, ioutil.ToStdOutOrFileOrNoop(deployConfigFilepath, 0o666)); err != nil { + return fmt.Errorf("failed to write rollup: %w", err) + } + + l1Contracts, err := L1(globalState, cfg.ChainID) + if err != nil { + return fmt.Errorf("failed to generate l1 contracts: %w", err) + } + + addressList, err := createAddressList(l1Contracts, globalState.AppliedIntent, cfg.ChainID) + if err != nil { + return fmt.Errorf("failed to create address list: %w", err) + } + + addressListFilepath := filepath.Join(cfg.Workdir, "addresses.json") + if err := jsonutil.WriteJSON(addressList, ioutil.ToStdOutOrFileOrNoop(addressListFilepath, 0o666)); err != nil { + return fmt.Errorf("failed to write address list: %w", err) + } + + envVars["SCR_GENESIS"] = genesisFilepath + envVars["SCR_ROLLUP_CONFIG"] = rollupFilepath + envVars["SCR_DEPLOY_CONFIG"] = deployConfigFilepath + envVars["SCR_DEPLOYMENTS_DIR"] = addressListFilepath + + envFilepath := filepath.Join(cfg.Workdir, "superchain-registry.env") + err = writeEnvFile(envFilepath, envVars) + if err != nil { + return fmt.Errorf("failed to write .env file: %w", err) + } + + fmt.Printf("---------------------------------------------------\n"+ + "Please populate any empty values in your .env file\n"+ + "before creating your pull-request to add this chain\n"+ + "to the superchain-registry repo.\n\n"+ + " * %s\n"+ + "---------------------------------------------------\n", envFilepath, + ) + + return nil +} + +func writeEnvFile(filepath string, envVars map[string]string) error { + file, err := os.Create(filepath) + if err != nil { + return err + } + defer file.Close() + + for key, value := range envVars { + _, err := file.WriteString(fmt.Sprintf("%s=\"%s\"\n", key, value)) + if err != nil { + return err + } + } + + return nil +} + +func createAddressList(l1Contracts *L1Contracts, appliedIntent *state.Intent, chainId common.Hash) (*superchain.AddressList, error) { + chainIntent, err := appliedIntent.Chain(chainId) + if err != nil { + return nil, fmt.Errorf("failed to get applied chain intent: %w", err) + } + + addressList := superchain.AddressList{ + // Roles + Roles: superchain.Roles{ + Guardian: superchain.Address(appliedIntent.SuperchainRoles.Guardian), + SystemConfigOwner: superchain.Address(chainIntent.Roles.SystemConfigOwner), + ProxyAdminOwner: superchain.Address(chainIntent.Roles.L1ProxyAdminOwner), + Challenger: superchain.Address(chainIntent.Roles.Challenger), + Proposer: superchain.Address(chainIntent.Roles.Proposer), + UnsafeBlockSigner: superchain.Address(chainIntent.Roles.UnsafeBlockSigner), + BatchSubmitter: superchain.Address(chainIntent.Roles.Batcher), + }, + + // Contracts + AddressManager: superchain.Address(l1Contracts.OpChainDeployment.AddressManagerAddress), + L1CrossDomainMessengerProxy: superchain.Address(l1Contracts.OpChainDeployment.L1CrossDomainMessengerProxyAddress), + L1ERC721BridgeProxy: superchain.Address(l1Contracts.OpChainDeployment.L1ERC721BridgeProxyAddress), + L1StandardBridgeProxy: superchain.Address(l1Contracts.OpChainDeployment.L1StandardBridgeProxyAddress), + OptimismMintableERC20FactoryProxy: superchain.Address(l1Contracts.OpChainDeployment.OptimismMintableERC20FactoryProxyAddress), + OptimismPortalProxy: superchain.Address(l1Contracts.OpChainDeployment.OptimismPortalProxyAddress), + SystemConfigProxy: superchain.Address(l1Contracts.OpChainDeployment.SystemConfigProxyAddress), + + ProxyAdmin: superchain.Address(l1Contracts.OpChainDeployment.ProxyAdminAddress), + SuperchainConfig: superchain.Address(l1Contracts.SuperchainDeployment.SuperchainConfigProxyAddress), + + // Fault proof contracts + AnchorStateRegistryProxy: superchain.Address(l1Contracts.OpChainDeployment.AnchorStateRegistryProxyAddress), + DelayedWETHProxy: superchain.Address(l1Contracts.OpChainDeployment.DelayedWETHPermissionedGameProxyAddress), + DisputeGameFactoryProxy: superchain.Address(l1Contracts.OpChainDeployment.DisputeGameFactoryProxyAddress), + FaultDisputeGame: superchain.Address(l1Contracts.OpChainDeployment.FaultDisputeGameAddress), + MIPS: superchain.Address(l1Contracts.ImplementationsDeployment.MipsSingletonAddress), + PermissionedDisputeGame: superchain.Address(l1Contracts.OpChainDeployment.PermissionedDisputeGameAddress), + PreimageOracle: superchain.Address(l1Contracts.ImplementationsDeployment.PreimageOracleSingletonAddress), + } + + return &addressList, nil +} diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go new file mode 100644 index 0000000000000..a6ae4dab3ad8d --- /dev/null +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -0,0 +1,907 @@ +package integration_test + +import ( + "bufio" + "bytes" + "compress/gzip" + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "log/slog" + "maps" + "math/big" + "os" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/retryproxy" + + altda "github.com/ethereum-optimism/optimism/op-alt-da" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect" + "github.com/ethereum-optimism/optimism/op-node/rollup" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-service/testutils/anvil" + "github.com/ethereum/go-ethereum/crypto" + + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + + "github.com/holiman/uint256" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils/kurtosisutil" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +const TestParams = ` +participants: + - el_type: geth + el_extra_params: + - "--gcmode=archive" + - "--rpc.txfeecap=0" + cl_type: lighthouse +network_params: + prefunded_accounts: '{ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { "balance": "1000000ETH" } }' + additional_preloaded_contracts: '{ + "0x4e59b44847b379578588920cA78FbF26c0B4956C": { + balance: "0ETH", + code: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + storage: {}, + nonce: 0, + secretKey: "0x" + } + }' + network_id: "77799777" + seconds_per_slot: 3 + genesis_delay: 0 +` + +const defaultL1ChainID uint64 = 77799777 + +type deployerKey struct{} + +func (d *deployerKey) HDPath() string { + return "m/44'/60'/0'/0/0" +} + +func (d *deployerKey) String() string { + return "deployer-key" +} + +func TestEndToEndApply(t *testing.T) { + op_e2e.InitParallel(t) + kurtosisutil.Test(t) + + lgr := testlog.Logger(t, slog.LevelDebug) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + enclaveCtx := kurtosisutil.StartEnclave(t, ctx, lgr, "github.com/ethpandaops/ethereum-package", TestParams) + + service, err := enclaveCtx.GetServiceContext("el-1-geth-lighthouse") + require.NoError(t, err) + + ip := service.GetMaybePublicIPAddress() + ports := service.GetPublicPorts() + rpcURL := fmt.Sprintf("http://%s:%d", ip, ports["rpc"].GetNumber()) + l1Client, err := ethclient.Dial(rpcURL) + require.NoError(t, err) + + pk, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") + require.NoError(t, err) + + l1ChainID := new(big.Int).SetUint64(defaultL1ChainID) + dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + + l2ChainID1 := uint256.NewInt(1) + l2ChainID2 := uint256.NewInt(2) + + loc, _ := testutil.LocalArtifacts(t) + + t.Run("two chains one after another", func(t *testing.T) { + intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) + cg := ethClientCodeGetter(ctx, l1Client) + + require.NoError(t, deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + L1RPCUrl: rpcURL, + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + }, + )) + + // create a new environment with wiped state to ensure we can continue using the + // state from the previous deployment + intent.Chains = append(intent.Chains, newChainIntent(t, dk, l1ChainID, l2ChainID2)) + + require.NoError(t, deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + L1RPCUrl: rpcURL, + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + }, + )) + + validateSuperchainDeployment(t, st, cg) + validateOPChainDeployment(t, cg, st, intent) + }) + + t.Run("chain with tagged artifacts", func(t *testing.T) { + intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) + intent.L1ContractsLocator = artifacts.DefaultL1ContractsLocator + intent.L2ContractsLocator = artifacts.DefaultL2ContractsLocator + + require.ErrorIs(t, deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + L1RPCUrl: rpcURL, + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + }, + ), pipeline.ErrRefusingToDeployTaggedReleaseWithoutOPCM) + }) +} + +func TestApplyExistingOPCM(t *testing.T) { + t.Run("mainnet", func(t *testing.T) { + testApplyExistingOPCM(t, 1, os.Getenv("MAINNET_RPC_URL"), standard.L1VersionsMainnet) + }) + t.Run("sepolia", func(t *testing.T) { + testApplyExistingOPCM(t, 11155111, os.Getenv("SEPOLIA_RPC_URL"), standard.L1VersionsSepolia) + }) +} + +func testApplyExistingOPCM(t *testing.T, l1ChainID uint64, forkRPCUrl string, versions standard.L1Versions) { + op_e2e.InitParallel(t) + + anvil.Test(t) + + if forkRPCUrl == "" { + t.Skip("no fork RPC URL provided") + } + + lgr := testlog.Logger(t, slog.LevelDebug) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + retryProxy := retryproxy.New(lgr, forkRPCUrl) + require.NoError(t, retryProxy.Start()) + t.Cleanup(func() { + require.NoError(t, retryProxy.Stop()) + }) + + runner, err := anvil.New( + retryProxy.Endpoint(), + lgr, + ) + require.NoError(t, err) + + require.NoError(t, runner.Start(ctx)) + t.Cleanup(func() { + require.NoError(t, runner.Stop()) + }) + + l1Client, err := ethclient.Dial(runner.RPCUrl()) + require.NoError(t, err) + + l1ChainIDBig := new(big.Int).SetUint64(l1ChainID) + dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + // index 0 from Anvil's test set + pk, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") + require.NoError(t, err) + + l2ChainID := uint256.NewInt(777) + + // Hardcode the below tags to ensure the test is validating the correct + // version even if the underlying tag changes + intent, st := newIntent( + t, + l1ChainIDBig, + dk, + l2ChainID, + artifacts.MustNewLocatorFromTag("op-contracts/v1.6.0"), + artifacts.MustNewLocatorFromTag("op-contracts/v1.7.0-beta.1+l2-contracts"), + ) + // Define a new create2 salt to avoid contract address collisions + _, err = rand.Read(st.Create2Salt[:]) + require.NoError(t, err) + + require.NoError(t, deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + L1RPCUrl: runner.RPCUrl(), + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + }, + )) + + validateOPChainDeployment(t, ethClientCodeGetter(ctx, l1Client), st, intent) + + releases := versions.Releases["op-contracts/v1.6.0"] + + implTests := []struct { + name string + expAddr common.Address + actAddr common.Address + }{ + {"OptimismPortal", releases.OptimismPortal.ImplementationAddress, st.ImplementationsDeployment.OptimismPortalImplAddress}, + {"SystemConfig,", releases.SystemConfig.ImplementationAddress, st.ImplementationsDeployment.SystemConfigImplAddress}, + {"L1CrossDomainMessenger", releases.L1CrossDomainMessenger.ImplementationAddress, st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress}, + {"L1ERC721Bridge", releases.L1ERC721Bridge.ImplementationAddress, st.ImplementationsDeployment.L1ERC721BridgeImplAddress}, + {"L1StandardBridge", releases.L1StandardBridge.ImplementationAddress, st.ImplementationsDeployment.L1StandardBridgeImplAddress}, + {"OptimismMintableERC20Factory", releases.OptimismMintableERC20Factory.ImplementationAddress, st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress}, + {"DisputeGameFactory", releases.DisputeGameFactory.ImplementationAddress, st.ImplementationsDeployment.DisputeGameFactoryImplAddress}, + {"MIPS", releases.MIPS.Address, st.ImplementationsDeployment.MipsSingletonAddress}, + {"PreimageOracle", releases.PreimageOracle.Address, st.ImplementationsDeployment.PreimageOracleSingletonAddress}, + {"DelayedWETH", releases.DelayedWETH.ImplementationAddress, st.ImplementationsDeployment.DelayedWETHImplAddress}, + } + for _, tt := range implTests { + require.Equal(t, tt.expAddr, tt.actAddr, "unexpected address for %s", tt.name) + } + + superchain, err := standard.SuperchainFor(l1ChainIDBig.Uint64()) + require.NoError(t, err) + + managerOwner, err := standard.ManagerOwnerAddrFor(l1ChainIDBig.Uint64()) + require.NoError(t, err) + + superchainTests := []struct { + name string + expAddr common.Address + actAddr common.Address + }{ + {"ProxyAdmin", managerOwner, st.SuperchainDeployment.ProxyAdminAddress}, + {"SuperchainConfig", common.Address(*superchain.Config.SuperchainConfigAddr), st.SuperchainDeployment.SuperchainConfigProxyAddress}, + {"ProtocolVersions", common.Address(*superchain.Config.ProtocolVersionsAddr), st.SuperchainDeployment.ProtocolVersionsProxyAddress}, + } + for _, tt := range superchainTests { + require.Equal(t, tt.expAddr, tt.actAddr, "unexpected address for %s", tt.name) + } + + artifactsFSL2, cleanupL2, err := artifacts.Download( + ctx, + intent.L2ContractsLocator, + artifacts.LogProgressor(lgr), + ) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, cleanupL2()) + }) + + chainState := st.Chains[0] + chainIntent := intent.Chains[0] + + semvers, err := inspect.L2Semvers(inspect.L2SemversConfig{ + Lgr: lgr, + Artifacts: artifactsFSL2, + ChainState: chainState, + }) + require.NoError(t, err) + + expectedSemversL2 := &inspect.L2PredeploySemvers{ + L2ToL1MessagePasser: "1.1.1-beta.1", + DeployerWhitelist: "1.1.1-beta.1", + WETH: "1.0.0-beta.1", + L2CrossDomainMessenger: "2.1.1-beta.1", + L2StandardBridge: "1.11.1-beta.1", + SequencerFeeVault: "1.5.0-beta.2", + OptimismMintableERC20Factory: "1.10.1-beta.2", + L1BlockNumber: "1.1.1-beta.1", + GasPriceOracle: "1.3.1-beta.1", + L1Block: "1.5.1-beta.1", + LegacyMessagePasser: "1.1.1-beta.1", + L2ERC721Bridge: "1.7.1-beta.2", + OptimismMintableERC721Factory: "1.4.1-beta.1", + BaseFeeVault: "1.5.0-beta.2", + L1FeeVault: "1.5.0-beta.2", + SchemaRegistry: "1.3.1-beta.1", + EAS: "1.4.1-beta.1", + CrossL2Inbox: "", + L2toL2CrossDomainMessenger: "", + SuperchainWETH: "", + ETHLiquidity: "", + SuperchainTokenBridge: "", + OptimismMintableERC20: "1.4.0-beta.1", + OptimismMintableERC721: "1.3.1-beta.1", + } + + require.EqualValues(t, expectedSemversL2, semvers) + + f, err := os.Open(fmt.Sprintf("./testdata/allocs-l2-v160-%d.json.gz", l1ChainID)) + require.NoError(t, err) + defer f.Close() + gzr, err := gzip.NewReader(f) + require.NoError(t, err) + defer gzr.Close() + dec := json.NewDecoder(bufio.NewReader(gzr)) + var expAllocs types.GenesisAlloc + require.NoError(t, dec.Decode(&expAllocs)) + + type storageCheckerFunc func(addr common.Address, actStorage map[common.Hash]common.Hash) + + storageDiff := func(addr common.Address, expStorage, actStorage map[common.Hash]common.Hash) { + require.EqualValues(t, expStorage, actStorage, "storage for %s differs", addr) + } + + defaultStorageChecker := func(addr common.Address, actStorage map[common.Hash]common.Hash) { + storageDiff(addr, expAllocs[addr].Storage, actStorage) + } + + overrideStorageChecker := func(addr common.Address, actStorage, overrides map[common.Hash]common.Hash) { + expStorage := make(map[common.Hash]common.Hash) + maps.Copy(expStorage, expAllocs[addr].Storage) + maps.Copy(expStorage, overrides) + storageDiff(addr, expStorage, actStorage) + } + + storageCheckers := map[common.Address]storageCheckerFunc{ + predeploys.L2CrossDomainMessengerAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) { + overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{ + {31: 0xcf}: common.BytesToHash(chainState.L1CrossDomainMessengerProxyAddress.Bytes()), + }) + }, + predeploys.L2StandardBridgeAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) { + overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{ + {31: 0x04}: common.BytesToHash(chainState.L1StandardBridgeProxyAddress.Bytes()), + }) + }, + predeploys.L2ERC721BridgeAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) { + overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{ + {31: 0x02}: common.BytesToHash(chainState.L1ERC721BridgeProxyAddress.Bytes()), + }) + }, + predeploys.ProxyAdminAddr: func(addr common.Address, actStorage map[common.Hash]common.Hash) { + overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{ + {}: common.BytesToHash(intent.Chains[0].Roles.L2ProxyAdminOwner.Bytes()), + }) + }, + // The ProxyAdmin owner is also set on the ProxyAdmin contract's implementation address, see + // L2Genesis.s.sol line 292. + common.HexToAddress("0xc0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30018"): func(addr common.Address, actStorage map[common.Hash]common.Hash) { + overrideStorageChecker(addr, actStorage, map[common.Hash]common.Hash{ + {}: common.BytesToHash(chainIntent.Roles.L2ProxyAdminOwner.Bytes()), + }) + }, + } + + //Use a custom equality function to compare the genesis allocs + //because the reflect-based one is really slow + actAllocs := st.Chains[0].Allocs.Data.Accounts + require.Equal(t, len(expAllocs), len(actAllocs)) + for addr, expAcc := range expAllocs { + actAcc, ok := actAllocs[addr] + require.True(t, ok) + require.True(t, expAcc.Balance.Cmp(actAcc.Balance) == 0, "balance for %s differs", addr) + require.Equal(t, expAcc.Nonce, actAcc.Nonce, "nonce for %s differs", addr) + require.Equal(t, hex.EncodeToString(expAllocs[addr].Code), hex.EncodeToString(actAcc.Code), "code for %s differs", addr) + + storageChecker, ok := storageCheckers[addr] + if !ok { + storageChecker = defaultStorageChecker + } + storageChecker(addr, actAcc.Storage) + } +} + +func TestL2BlockTimeOverride(t *testing.T) { + op_e2e.InitParallel(t) + kurtosisutil.Test(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + opts, intent, st := setupGenesisChain(t, defaultL1ChainID) + intent.GlobalDeployOverrides = map[string]interface{}{ + "l2BlockTime": float64(3), + } + + require.NoError(t, deployer.ApplyPipeline(ctx, opts)) + + cfg, err := state.CombineDeployConfig(intent, intent.Chains[0], st, st.Chains[0]) + require.NoError(t, err) + require.Equal(t, uint64(3), cfg.L2InitializationConfig.L2CoreDeployConfig.L2BlockTime, "L2 block time should be 3 seconds") +} + +func TestApplyGenesisStrategy(t *testing.T) { + op_e2e.InitParallel(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + opts, intent, st := setupGenesisChain(t, defaultL1ChainID) + + require.NoError(t, deployer.ApplyPipeline(ctx, opts)) + + cg := stateDumpCodeGetter(st) + validateSuperchainDeployment(t, st, cg) + + for i := range intent.Chains { + t.Run(fmt.Sprintf("chain-%d", i), func(t *testing.T) { + validateOPChainDeployment(t, cg, st, intent) + }) + } +} + +func TestProofParamOverrides(t *testing.T) { + op_e2e.InitParallel(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + opts, intent, st := setupGenesisChain(t, defaultL1ChainID) + intent.GlobalDeployOverrides = map[string]any{ + "withdrawalDelaySeconds": standard.WithdrawalDelaySeconds + 1, + "minProposalSizeBytes": standard.MinProposalSizeBytes + 1, + "challengePeriodSeconds": standard.ChallengePeriodSeconds + 1, + "proofMaturityDelaySeconds": standard.ProofMaturityDelaySeconds + 1, + "disputeGameFinalityDelaySeconds": standard.DisputeGameFinalityDelaySeconds + 1, + "mipsVersion": standard.MIPSVersion + 1, + "disputeGameType": standard.DisputeGameType, // This must be set to the permissioned game + "disputeAbsolutePrestate": common.Hash{'A', 'B', 'S', 'O', 'L', 'U', 'T', 'E'}, + "disputeMaxGameDepth": standard.DisputeMaxGameDepth + 1, + "disputeSplitDepth": standard.DisputeSplitDepth + 1, + "disputeClockExtension": standard.DisputeClockExtension + 1, + "disputeMaxClockDuration": standard.DisputeMaxClockDuration + 1, + "dangerouslyAllowCustomDisputeParameters": true, + } + + require.NoError(t, deployer.ApplyPipeline(ctx, opts)) + + allocs := st.L1StateDump.Data.Accounts + chainState := st.Chains[0] + + uint64Caster := func(t *testing.T, val any) common.Hash { + return common.BigToHash(new(big.Int).SetUint64(val.(uint64))) + } + + tests := []struct { + name string + caster func(t *testing.T, val any) common.Hash + address common.Address + }{ + { + "withdrawalDelaySeconds", + uint64Caster, + st.ImplementationsDeployment.DelayedWETHImplAddress, + }, + { + "minProposalSizeBytes", + uint64Caster, + st.ImplementationsDeployment.PreimageOracleSingletonAddress, + }, + { + "challengePeriodSeconds", + uint64Caster, + st.ImplementationsDeployment.PreimageOracleSingletonAddress, + }, + { + "proofMaturityDelaySeconds", + uint64Caster, + st.ImplementationsDeployment.OptimismPortalImplAddress, + }, + { + "disputeGameFinalityDelaySeconds", + uint64Caster, + st.ImplementationsDeployment.OptimismPortalImplAddress, + }, + { + "disputeAbsolutePrestate", + func(t *testing.T, val any) common.Hash { + return val.(common.Hash) + }, + chainState.PermissionedDisputeGameAddress, + }, + { + "disputeMaxGameDepth", + uint64Caster, + chainState.PermissionedDisputeGameAddress, + }, + { + "disputeSplitDepth", + uint64Caster, + chainState.PermissionedDisputeGameAddress, + }, + { + "disputeClockExtension", + uint64Caster, + chainState.PermissionedDisputeGameAddress, + }, + { + "disputeMaxClockDuration", + uint64Caster, + chainState.PermissionedDisputeGameAddress, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkImmutable(t, allocs, tt.address, tt.caster(t, intent.GlobalDeployOverrides[tt.name])) + }) + } +} + +func TestInteropDeployment(t *testing.T) { + op_e2e.InitParallel(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + opts, intent, st := setupGenesisChain(t, defaultL1ChainID) + intent.UseInterop = true + + require.NoError(t, deployer.ApplyPipeline(ctx, opts)) + + chainState := st.Chains[0] + depManagerSlot := common.HexToHash("0x1708e077affb93e89be2665fb0fb72581be66f84dc00d25fed755ae911905b1c") + checkImmutable(t, st.L1StateDump.Data.Accounts, st.ImplementationsDeployment.SystemConfigImplAddress, depManagerSlot) + proxyAdminOwnerHash := common.BytesToHash(intent.Chains[0].Roles.SystemConfigOwner.Bytes()) + checkStorageSlot(t, st.L1StateDump.Data.Accounts, chainState.SystemConfigProxyAddress, depManagerSlot, proxyAdminOwnerHash) +} + +func TestAltDADeployment(t *testing.T) { + op_e2e.InitParallel(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + opts, intent, st := setupGenesisChain(t, defaultL1ChainID) + altDACfg := genesis.AltDADeployConfig{ + UseAltDA: true, + DACommitmentType: altda.KeccakCommitmentString, + DAChallengeWindow: 10, + DAResolveWindow: 10, + DABondSize: 100, + DAResolverRefundPercentage: 50, + } + intent.Chains[0].DangerousAltDAConfig = altDACfg + + require.NoError(t, deployer.ApplyPipeline(ctx, opts)) + + chainState := st.Chains[0] + require.NotEmpty(t, chainState.DataAvailabilityChallengeProxyAddress) + require.NotEmpty(t, chainState.DataAvailabilityChallengeImplAddress) + + _, rollupCfg, err := inspect.GenesisAndRollup(st, chainState.ID) + require.NoError(t, err) + require.EqualValues(t, &rollup.AltDAConfig{ + CommitmentType: altda.KeccakCommitmentString, + DAChallengeWindow: altDACfg.DAChallengeWindow, + DAChallengeAddress: chainState.DataAvailabilityChallengeProxyAddress, + DAResolveWindow: altDACfg.DAResolveWindow, + }, rollupCfg.AltDAConfig) +} + +func TestInvalidL2Genesis(t *testing.T) { + op_e2e.InitParallel(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // these tests were generated by grepping all usages of the deploy + // config in L2Genesis.s.sol. + tests := []struct { + name string + overrides map[string]any + }{ + { + name: "L2 proxy admin owner not set", + overrides: map[string]any{ + "proxyAdminOwner": nil, + }, + }, + { + name: "base fee vault recipient not set", + overrides: map[string]any{ + "baseFeeVaultRecipient": nil, + }, + }, + { + name: "l1 fee vault recipient not set", + overrides: map[string]any{ + "l1FeeVaultRecipient": nil, + }, + }, + { + name: "sequencer fee vault recipient not set", + overrides: map[string]any{ + "sequencerFeeVaultRecipient": nil, + }, + }, + { + name: "l1 chain ID not set", + overrides: map[string]any{ + "l1ChainID": nil, + }, + }, + { + name: "l2 chain ID not set", + overrides: map[string]any{ + "l2ChainID": nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts, intent, _ := setupGenesisChain(t, defaultL1ChainID) + intent.DeploymentStrategy = state.DeploymentStrategyGenesis + intent.GlobalDeployOverrides = tt.overrides + + err := deployer.ApplyPipeline(ctx, opts) + require.Error(t, err) + require.ErrorContains(t, err, "failed to combine L2 init config") + }) + } +} + +func setupGenesisChain(t *testing.T, l1ChainID uint64) (deployer.ApplyPipelineOpts, *state.Intent, *state.State) { + lgr := testlog.Logger(t, slog.LevelDebug) + + depKey := new(deployerKey) + l1ChainIDBig := new(big.Int).SetUint64(l1ChainID) + dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + + l2ChainID1 := uint256.NewInt(1) + + priv, err := dk.Secret(depKey) + require.NoError(t, err) + + loc, _ := testutil.LocalArtifacts(t) + + intent, st := newIntent(t, l1ChainIDBig, dk, l2ChainID1, loc, loc) + intent.Chains = append(intent.Chains, newChainIntent(t, dk, l1ChainIDBig, l2ChainID1)) + intent.DeploymentStrategy = state.DeploymentStrategyGenesis + + opts := deployer.ApplyPipelineOpts{ + DeployerPrivateKey: priv, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + } + + return opts, intent, st +} + +func addrFor(t *testing.T, dk *devkeys.MnemonicDevKeys, key devkeys.Key) common.Address { + addr, err := dk.Address(key) + require.NoError(t, err) + return addr +} + +func newIntent( + t *testing.T, + l1ChainID *big.Int, + dk *devkeys.MnemonicDevKeys, + l2ChainID *uint256.Int, + l1Loc *artifacts.Locator, + l2Loc *artifacts.Locator, +) (*state.Intent, *state.State) { + intent := &state.Intent{ + ConfigType: state.IntentConfigTypeCustom, + DeploymentStrategy: state.DeploymentStrategyLive, + L1ChainID: l1ChainID.Uint64(), + SuperchainRoles: &state.SuperchainRoles{ + ProxyAdminOwner: addrFor(t, dk, devkeys.L1ProxyAdminOwnerRole.Key(l1ChainID)), + ProtocolVersionsOwner: addrFor(t, dk, devkeys.SuperchainDeployerKey.Key(l1ChainID)), + Guardian: addrFor(t, dk, devkeys.SuperchainConfigGuardianKey.Key(l1ChainID)), + }, + FundDevAccounts: true, + L1ContractsLocator: l1Loc, + L2ContractsLocator: l2Loc, + Chains: []*state.ChainIntent{ + newChainIntent(t, dk, l1ChainID, l2ChainID), + }, + } + st := &state.State{ + Version: 1, + } + return intent, st +} + +func newChainIntent(t *testing.T, dk *devkeys.MnemonicDevKeys, l1ChainID *big.Int, l2ChainID *uint256.Int) *state.ChainIntent { + return &state.ChainIntent{ + ID: l2ChainID.Bytes32(), + BaseFeeVaultRecipient: addrFor(t, dk, devkeys.BaseFeeVaultRecipientRole.Key(l1ChainID)), + L1FeeVaultRecipient: addrFor(t, dk, devkeys.L1FeeVaultRecipientRole.Key(l1ChainID)), + SequencerFeeVaultRecipient: addrFor(t, dk, devkeys.SequencerFeeVaultRecipientRole.Key(l1ChainID)), + Eip1559DenominatorCanyon: standard.Eip1559DenominatorCanyon, + Eip1559Denominator: standard.Eip1559Denominator, + Eip1559Elasticity: standard.Eip1559Elasticity, + Roles: state.ChainRoles{ + L1ProxyAdminOwner: addrFor(t, dk, devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)), + L2ProxyAdminOwner: addrFor(t, dk, devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)), + SystemConfigOwner: addrFor(t, dk, devkeys.SystemConfigOwner.Key(l1ChainID)), + UnsafeBlockSigner: addrFor(t, dk, devkeys.SequencerP2PRole.Key(l1ChainID)), + Batcher: addrFor(t, dk, devkeys.BatcherRole.Key(l1ChainID)), + Proposer: addrFor(t, dk, devkeys.ProposerRole.Key(l1ChainID)), + Challenger: addrFor(t, dk, devkeys.ChallengerRole.Key(l1ChainID)), + }, + } +} + +type codeGetter func(t *testing.T, addr common.Address) []byte + +func ethClientCodeGetter(ctx context.Context, client *ethclient.Client) codeGetter { + return func(t *testing.T, addr common.Address) []byte { + code, err := client.CodeAt(ctx, addr, nil) + require.NoError(t, err) + return code + } +} + +func stateDumpCodeGetter(st *state.State) codeGetter { + return func(t *testing.T, addr common.Address) []byte { + acc, ok := st.L1StateDump.Data.Accounts[addr] + require.True(t, ok, "no account found for address %s", addr) + return acc.Code + } +} + +func validateSuperchainDeployment(t *testing.T, st *state.State, cg codeGetter) { + addrs := []struct { + name string + addr common.Address + }{ + {"SuperchainProxyAdmin", st.SuperchainDeployment.ProxyAdminAddress}, + {"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress}, + {"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress}, + {"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress}, + {"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress}, + {"Opcm", st.ImplementationsDeployment.OpcmAddress}, + {"PreimageOracleSingleton", st.ImplementationsDeployment.PreimageOracleSingletonAddress}, + {"MipsSingleton", st.ImplementationsDeployment.MipsSingletonAddress}, + } + for _, addr := range addrs { + t.Run(addr.name, func(t *testing.T) { + code := cg(t, addr.addr) + require.NotEmpty(t, code, "contract %s at %s has no code", addr.name, addr.addr) + }) + } +} + +func validateOPChainDeployment(t *testing.T, cg codeGetter, st *state.State, intent *state.Intent) { + // Validate that the implementation addresses are always set, even in subsequent deployments + // that pull from an existing OPCM deployment. + implAddrs := []struct { + name string + addr common.Address + }{ + {"DelayedWETHImplAddress", st.ImplementationsDeployment.DelayedWETHImplAddress}, + {"OptimismPortalImplAddress", st.ImplementationsDeployment.OptimismPortalImplAddress}, + {"SystemConfigImplAddress", st.ImplementationsDeployment.SystemConfigImplAddress}, + {"L1CrossDomainMessengerImplAddress", st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress}, + {"L1ERC721BridgeImplAddress", st.ImplementationsDeployment.L1ERC721BridgeImplAddress}, + {"L1StandardBridgeImplAddress", st.ImplementationsDeployment.L1StandardBridgeImplAddress}, + {"OptimismMintableERC20FactoryImplAddress", st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress}, + {"DisputeGameFactoryImplAddress", st.ImplementationsDeployment.DisputeGameFactoryImplAddress}, + {"MipsSingletonAddress", st.ImplementationsDeployment.MipsSingletonAddress}, + {"PreimageOracleSingletonAddress", st.ImplementationsDeployment.PreimageOracleSingletonAddress}, + } + for _, addr := range implAddrs { + require.NotEmpty(t, addr.addr, "%s should be set", addr.name) + code := cg(t, addr.addr) + require.NotEmpty(t, code, "contract %s at %s has no code", addr.name, addr.addr) + } + + for i, chainState := range st.Chains { + chainAddrs := []struct { + name string + addr common.Address + }{ + {"ProxyAdminAddress", chainState.ProxyAdminAddress}, + {"AddressManagerAddress", chainState.AddressManagerAddress}, + {"L1ERC721BridgeProxyAddress", chainState.L1ERC721BridgeProxyAddress}, + {"SystemConfigProxyAddress", chainState.SystemConfigProxyAddress}, + {"OptimismMintableERC20FactoryProxyAddress", chainState.OptimismMintableERC20FactoryProxyAddress}, + {"L1StandardBridgeProxyAddress", chainState.L1StandardBridgeProxyAddress}, + {"L1CrossDomainMessengerProxyAddress", chainState.L1CrossDomainMessengerProxyAddress}, + {"OptimismPortalProxyAddress", chainState.OptimismPortalProxyAddress}, + {"DisputeGameFactoryProxyAddress", chainState.DisputeGameFactoryProxyAddress}, + {"AnchorStateRegistryProxyAddress", chainState.AnchorStateRegistryProxyAddress}, + {"FaultDisputeGameAddress", chainState.FaultDisputeGameAddress}, + {"PermissionedDisputeGameAddress", chainState.PermissionedDisputeGameAddress}, + {"DelayedWETHPermissionedGameProxyAddress", chainState.DelayedWETHPermissionedGameProxyAddress}, + // {"DelayedWETHPermissionlessGameProxyAddress", chainState.DelayedWETHPermissionlessGameProxyAddress}, + } + for _, addr := range chainAddrs { + // TODO Delete this `if`` block once FaultDisputeGameAddress is deployed. + if addr.name == "FaultDisputeGameAddress" { + continue + } + code := cg(t, addr.addr) + require.NotEmpty(t, code, "contract %s at %s for chain %s has no code", addr.name, addr.addr, chainState.ID) + } + + alloc := chainState.Allocs.Data.Accounts + + chainIntent := intent.Chains[i] + checkImmutableBehindProxy(t, alloc, predeploys.BaseFeeVaultAddr, chainIntent.BaseFeeVaultRecipient) + checkImmutableBehindProxy(t, alloc, predeploys.L1FeeVaultAddr, chainIntent.L1FeeVaultRecipient) + checkImmutableBehindProxy(t, alloc, predeploys.SequencerFeeVaultAddr, chainIntent.SequencerFeeVaultRecipient) + checkImmutableBehindProxy(t, alloc, predeploys.OptimismMintableERC721FactoryAddr, common.BigToHash(new(big.Int).SetUint64(intent.L1ChainID))) + + // ownership slots + var addrAsSlot common.Hash + addrAsSlot.SetBytes(chainIntent.Roles.L1ProxyAdminOwner.Bytes()) + // slot 0 + ownerSlot := common.Hash{} + checkStorageSlot(t, alloc, predeploys.ProxyAdminAddr, ownerSlot, addrAsSlot) + var defaultGovOwner common.Hash + defaultGovOwner.SetBytes(common.HexToAddress("0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAdDEad").Bytes()) + checkStorageSlot(t, alloc, predeploys.GovernanceTokenAddr, common.Hash{31: 0x0a}, defaultGovOwner) + + require.Equal(t, int(chainIntent.Eip1559Denominator), 50, "EIP1559Denominator should be set") + require.Equal(t, int(chainIntent.Eip1559Elasticity), 6, "EIP1559Elasticity should be set") + } +} + +func getEIP1967ImplementationAddress(t *testing.T, allocations types.GenesisAlloc, proxyAddress common.Address) common.Address { + storage := allocations[proxyAddress].Storage + storageValue := storage[genesis.ImplementationSlot] + require.NotEmpty(t, storageValue, "Implementation address for %s should be set", proxyAddress) + return common.HexToAddress(storageValue.Hex()) +} + +type bytesMarshaler interface { + Bytes() []byte +} + +func checkImmutableBehindProxy(t *testing.T, allocations types.GenesisAlloc, proxyContract common.Address, thing bytesMarshaler) { + implementationAddress := getEIP1967ImplementationAddress(t, allocations, proxyContract) + checkImmutable(t, allocations, implementationAddress, thing) +} + +func checkImmutable(t *testing.T, allocations types.GenesisAlloc, implementationAddress common.Address, thing bytesMarshaler) { + account, ok := allocations[implementationAddress] + require.True(t, ok, "%s not found in allocations", implementationAddress) + require.NotEmpty(t, account.Code, "%s should have code", implementationAddress) + require.True( + t, + bytes.Contains(account.Code, thing.Bytes()), + "%s code should contain %s immutable", implementationAddress, hex.EncodeToString(thing.Bytes()), + ) +} + +func checkStorageSlot(t *testing.T, allocs types.GenesisAlloc, address common.Address, slot common.Hash, expected common.Hash) { + account, ok := allocs[address] + require.True(t, ok, "account not found for address %s", address) + value, ok := account.Storage[slot] + if expected == (common.Hash{}) { + require.False(t, ok, "slot %s for account %s should not be set", slot, address) + return + } + require.True(t, ok, "slot %s not found for account %s", slot, address) + require.Equal(t, expected, value, "slot %s for account %s should be %s", slot, address, expected) +} diff --git a/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160-1.json.gz b/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160-1.json.gz new file mode 100644 index 0000000000000..7a5450d641911 Binary files /dev/null and b/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160-1.json.gz differ diff --git a/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160-11155111.json.gz b/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160-11155111.json.gz new file mode 100644 index 0000000000000..2fed12ccec471 Binary files /dev/null and b/op-deployer/pkg/deployer/integration_test/testdata/allocs-l2-v160-11155111.json.gz differ diff --git a/op-deployer/pkg/deployer/opcm/alt_da.go b/op-deployer/pkg/deployer/opcm/alt_da.go new file mode 100644 index 0000000000000..7c05a42a7a5a2 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/alt_da.go @@ -0,0 +1,63 @@ +package opcm + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum/go-ethereum/common" +) + +type DeployAltDAInput struct { + Salt common.Hash + ProxyAdmin common.Address + ChallengeContractOwner common.Address + ChallengeWindow *big.Int + ResolveWindow *big.Int + BondSize *big.Int + ResolverRefundPercentage *big.Int +} + +type DeployAltDAOutput struct { + DataAvailabilityChallengeProxy common.Address + DataAvailabilityChallengeImpl common.Address +} + +type DeployAltDAScript struct { + Run func(input, output common.Address) error +} + +func DeployAltDA( + host *script.Host, + input DeployAltDAInput, +) (DeployAltDAOutput, error) { + var output DeployAltDAOutput + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() + + cleanupInput, err := script.WithPrecompileAtAddress[*DeployAltDAInput](host, inputAddr, &input) + if err != nil { + return output, fmt.Errorf("failed to insert DeployAltDAInput precompile: %w", err) + } + defer cleanupInput() + + cleanupOutput, err := script.WithPrecompileAtAddress[*DeployAltDAOutput](host, outputAddr, &output, + script.WithFieldSetter[*DeployAltDAOutput]) + if err != nil { + return output, fmt.Errorf("failed to insert DeployAltDAOutput precompile: %w", err) + } + defer cleanupOutput() + + implContract := "DeployAltDA" + deployScript, cleanupDeploy, err := script.WithScript[DeployAltDAScript](host, "DeployAltDA.s.sol", implContract) + if err != nil { + return output, fmt.Errorf("failed to laod %s script: %w", implContract, err) + } + defer cleanupDeploy() + + if err := deployScript.Run(inputAddr, outputAddr); err != nil { + return output, fmt.Errorf("failed to run %s script: %w", implContract, err) + } + + return output, nil +} diff --git a/op-deployer/pkg/deployer/opcm/alt_da_test.go b/op-deployer/pkg/deployer/opcm/alt_da_test.go new file mode 100644 index 0000000000000..97e2b485b900e --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/alt_da_test.go @@ -0,0 +1,42 @@ +package opcm + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestDeployAltDA(t *testing.T) { + _, artifacts := testutil.LocalArtifacts(t) + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + testlog.Logger(t, log.LevelInfo), + common.Address{'D'}, + artifacts, + ) + require.NoError(t, err) + + input := DeployAltDAInput{ + Salt: common.HexToHash("0x1234"), + ProxyAdmin: common.Address{'P'}, + ChallengeContractOwner: common.Address{'O'}, + ChallengeWindow: big.NewInt(100), + ResolveWindow: big.NewInt(200), + BondSize: big.NewInt(300), + ResolverRefundPercentage: big.NewInt(50), // must be < 100 + } + + output, err := DeployAltDA(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.DataAvailabilityChallengeProxy) + require.NotEmpty(t, output.DataAvailabilityChallengeImpl) +} diff --git a/op-deployer/pkg/deployer/opcm/asterisc.go b/op-deployer/pkg/deployer/opcm/asterisc.go new file mode 100644 index 0000000000000..053da43b42589 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/asterisc.go @@ -0,0 +1,30 @@ +package opcm + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +type DeployAsteriscInput struct { + PreimageOracle common.Address +} + +func (input *DeployAsteriscInput) InputSet() bool { + return true +} + +type DeployAsteriscOutput struct { + AsteriscSingleton common.Address +} + +func (output *DeployAsteriscOutput) CheckOutput(input common.Address) error { + return nil +} + +func DeployAsterisc( + host *script.Host, + input DeployAsteriscInput, +) (DeployAsteriscOutput, error) { + return RunBasicScript[DeployAsteriscInput, DeployAsteriscOutput](host, input, "DeployAsterisc.s.sol", "DeployAsterisc") +} diff --git a/op-deployer/pkg/deployer/opcm/asterisc_test.go b/op-deployer/pkg/deployer/opcm/asterisc_test.go new file mode 100644 index 0000000000000..0ed68f7aeaff0 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/asterisc_test.go @@ -0,0 +1,34 @@ +package opcm + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestDeployAsterisc(t *testing.T) { + _, artifacts := testutil.LocalArtifacts(t) + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + testlog.Logger(t, log.LevelInfo), + common.Address{'D'}, + artifacts, + ) + require.NoError(t, err) + + input := DeployAsteriscInput{ + PreimageOracle: common.Address{0xab}, + } + + output, err := DeployAsterisc(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.AsteriscSingleton) +} diff --git a/op-chain-ops/deployer/opcm/contract.go b/op-deployer/pkg/deployer/opcm/contract.go similarity index 68% rename from op-chain-ops/deployer/opcm/contract.go rename to op-deployer/pkg/deployer/opcm/contract.go index c81222aafe888..8d02f77e8e0ac 100644 --- a/op-chain-ops/deployer/opcm/contract.go +++ b/op-deployer/pkg/deployer/opcm/contract.go @@ -29,14 +29,34 @@ func (c *Contract) ProtocolVersions(ctx context.Context) (common.Address, error) } func (c *Contract) getAddress(ctx context.Context, name string) (common.Address, error) { + return c.callContractMethod(ctx, name, abi.Arguments{}) +} + +// Used to call getAddress(string) on legacy AddressManager contract +func (c *Contract) GetAddressByNameViaAddressManager(ctx context.Context, name string) (common.Address, error) { + inputs := abi.Arguments{ + abi.Argument{ + Name: "_name", + Type: mustType("string"), + Indexed: false, + }, + } + return c.callContractMethod(ctx, "getAddress", inputs, name) +} + +func (c *Contract) GenericAddressGetter(ctx context.Context, functionName string) (common.Address, error) { + return c.callContractMethod(ctx, functionName, abi.Arguments{}) +} + +func (c *Contract) callContractMethod(ctx context.Context, methodName string, inputs abi.Arguments, args ...interface{}) (common.Address, error) { method := abi.NewMethod( - name, - name, + methodName, + methodName, abi.Function, "view", true, false, - abi.Arguments{}, + inputs, abi.Arguments{ abi.Argument{ Name: "address", @@ -46,7 +66,7 @@ func (c *Contract) getAddress(ctx context.Context, name string) (common.Address, }, ) - calldata, err := method.Inputs.Pack() + calldata, err := method.Inputs.Pack(args...) if err != nil { return common.Address{}, fmt.Errorf("failed to pack inputs: %w", err) } diff --git a/op-deployer/pkg/deployer/opcm/delayed_weth.go b/op-deployer/pkg/deployer/opcm/delayed_weth.go new file mode 100644 index 0000000000000..06f533956f891 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/delayed_weth.go @@ -0,0 +1,38 @@ +package opcm + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +type DeployDelayedWETHInput struct { + Release string + ProxyAdmin common.Address + SuperchainConfigProxy common.Address + DelayedWethImpl common.Address + DelayedWethOwner common.Address + DelayedWethDelay *big.Int +} + +func (input *DeployDelayedWETHInput) InputSet() bool { + return true +} + +type DeployDelayedWETHOutput struct { + DelayedWethImpl common.Address + DelayedWethProxy common.Address +} + +func (output *DeployDelayedWETHOutput) CheckOutput(input common.Address) error { + return nil +} + +func DeployDelayedWETH( + host *script.Host, + input DeployDelayedWETHInput, +) (DeployDelayedWETHOutput, error) { + return RunBasicScript[DeployDelayedWETHInput, DeployDelayedWETHOutput](host, input, "DeployDelayedWETH.s.sol", "DeployDelayedWETH") +} diff --git a/op-deployer/pkg/deployer/opcm/delayed_weth_test.go b/op-deployer/pkg/deployer/opcm/delayed_weth_test.go new file mode 100644 index 0000000000000..3a8be1b3e0d6f --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/delayed_weth_test.go @@ -0,0 +1,59 @@ +package opcm + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestDeployDelayedWETH(t *testing.T) { + _, artifacts := testutil.LocalArtifacts(t) + + testCases := []struct { + TestName string + Impl common.Address + }{ + { + TestName: "ExistingImpl", + Impl: common.Address{'I'}, + }, + { + TestName: "NoExistingImpl", + Impl: common.Address{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + testlog.Logger(t, log.LevelInfo), + common.Address{'D'}, + artifacts, + ) + require.NoError(t, err) + + input := DeployDelayedWETHInput{ + Release: "dev", + ProxyAdmin: common.Address{'P'}, + SuperchainConfigProxy: common.Address{'S'}, + DelayedWethImpl: testCase.Impl, + DelayedWethOwner: common.Address{'O'}, + DelayedWethDelay: big.NewInt(100), + } + + output, err := DeployDelayedWETH(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.DelayedWethImpl) + require.NotEmpty(t, output.DelayedWethProxy) + }) + } +} diff --git a/op-deployer/pkg/deployer/opcm/dispute_game.go b/op-deployer/pkg/deployer/opcm/dispute_game.go new file mode 100644 index 0000000000000..7128a08e8b025 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/dispute_game.go @@ -0,0 +1,48 @@ +package opcm + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +type DeployDisputeGameInput struct { + Release string + StandardVersionsToml string + VmAddress common.Address + GameKind string + GameType uint32 + AbsolutePrestate common.Hash + MaxGameDepth uint64 + SplitDepth uint64 + ClockExtension uint64 + MaxClockDuration uint64 + DelayedWethProxy common.Address + AnchorStateRegistryProxy common.Address + L2ChainId uint64 + Proposer common.Address + Challenger common.Address +} + +func (input *DeployDisputeGameInput) InputSet() bool { + return true +} + +type DeployDisputeGameOutput struct { + DisputeGameImpl common.Address +} + +func (output *DeployDisputeGameOutput) CheckOutput(input common.Address) error { + return nil +} + +type DeployDisputeGameScript struct { + Run func(input, output common.Address) error +} + +func DeployDisputeGame( + host *script.Host, + input DeployDisputeGameInput, +) (DeployDisputeGameOutput, error) { + return RunBasicScript[DeployDisputeGameInput, DeployDisputeGameOutput](host, input, "DeployDisputeGame.s.sol", "DeployDisputeGame") +} diff --git a/op-deployer/pkg/deployer/opcm/dispute_game_test.go b/op-deployer/pkg/deployer/opcm/dispute_game_test.go new file mode 100644 index 0000000000000..2b2d33f9efe08 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/dispute_game_test.go @@ -0,0 +1,61 @@ +package opcm + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "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/stretchr/testify/require" +) + +func TestDeployDisputeGame(t *testing.T) { + _, artifacts := testutil.LocalArtifacts(t) + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + testlog.Logger(t, log.LevelInfo), + common.Address{'D'}, + artifacts, + ) + require.NoError(t, err) + + standardVersionsTOML, err := standard.L1VersionsDataFor(11155111) + require.NoError(t, err) + vmAddr := common.Address{'V'} + host.ImportAccount(vmAddr, types.Account{Code: vmCode}) + // Address has to match the one returned by vmCode for oracle()(address) + host.ImportAccount(common.HexToAddress("0x92240135b46fc1142dA181f550aE8f595B858854"), types.Account{Code: oracleCode}) + + input := DeployDisputeGameInput{ + Release: "dev", + StandardVersionsToml: standardVersionsTOML, + VmAddress: vmAddr, + GameKind: "PermissionedDisputeGame", + GameType: 1, + AbsolutePrestate: common.Hash{'A'}, + MaxGameDepth: standard.DisputeMaxGameDepth, + SplitDepth: standard.DisputeSplitDepth, + ClockExtension: standard.DisputeClockExtension, + MaxClockDuration: standard.DisputeMaxClockDuration, + DelayedWethProxy: common.Address{'D'}, + AnchorStateRegistryProxy: common.Address{'A'}, + L2ChainId: 69, + Proposer: common.Address{'P'}, + Challenger: common.Address{'C'}, + } + + output, err := DeployDisputeGame(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.DisputeGameImpl) +} + +// Code to etch so that the VM oracle() method and the oracle challengePeriod() methods work (they return immutables) +var vmCode = common.FromHex("0x608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d50146100465780637dc0d1d014610098578063e14ced32146100dc575b600080fd5b6100826040518060400160405280600c81526020017f312e322e312d626574612e37000000000000000000000000000000000000000081525081565b60405161008f919061269c565b60405180910390f35b60405173ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000092240135b46fc1142da181f550ae8f595b85885416815260200161008f565b6100ef6100ea366004612751565b6100fd565b60405190815260200161008f565b6000610107612612565b6080811461011457600080fd5b6040516106001461012457600080fd5b6084871461013157600080fd5b6101a4851461013f57600080fd5b8635608052602087013560a052604087013560e090811c60c09081526044890135821c82526048890135821c61010052604c890135821c610120526050890135821c61014052605489013590911c61016052605888013560f890811c610180526059890135901c6101a0819052605a89013590911c6101c05260628801906101e09060018111156101f5576040517f0136cc76000000000000000000000000000000000000000000000000000000008152600481fd5b506020810181511461020657600080fd5b60200160005b602081101561023057823560e01c825260049092019160209091019060010161020c565b5050508061012001511561024e57610246610408565b9150506103ff565b6101408101805160010167ffffffffffffffff16905260006101a4905060008060006102838560600151866000015186610559565b9250925092508163ffffffff1660001480156102a557508063ffffffff16600c145b156102bf576102b387610583565b955050505050506103ff565b63ffffffff8216603014806102da575063ffffffff82166038145b156102ea576102b385848461095d565b6000610364866040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b6040805160e0810182528281526101608901516020820152885191810191909152610524606082015263ffffffff808716608083015285811660a0830152841660c08201529091506103b581610b78565b50508752815163ffffffff9081166060808a01919091526020840151821660808a01526040840151821660a08a01528301511660c08801526103f5610408565b9750505050505050505b95945050505050565b60408051608051815260a051602082015260dc519181019190915260fc51604482015261011c51604882015261013c51604c82015261015c51605082015261017c5160548201526101805161019f5160588301526101a0516101bf5160598401526101d851605a84015260009261020092909160628301919060018111156104b5576040517f0136cc76000000000000000000000000000000000000000000000000000000008152600481fd5b60005b60208110156104dc57601c86015184526020909501946004909301926001016104b8565b506000835283830384a06000945080600181146104fc5760039550610524565b828015610514576001811461051d5760029650610522565b60009650610522565b600196505b505b50505081900390207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f89190911b17919050565b6000806000610569858786611001565b925050603f601a83901c8116915082165b93509350939050565b600061058d612612565b608090506000806000806105c48561016001516040810151608082015160a083015160c084015160e0909401519294919390929091565b509350935093509350600080610ffa63ffffffff168663ffffffff1603610609576105f485858960e00151611053565b63ffffffff1660e08a0152909250905061086c565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03363ffffffff871601610642576340000000915061086c565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefe863ffffffff871601610678576001915061086c565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef6a63ffffffff8716016106cc57600161012088015260ff85166101008801526106bf610408565b9998505050505050505050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff05d63ffffffff8716016107ca5760006040518061012001604052808763ffffffff1681526020018663ffffffff1681526020018563ffffffff16815260200189602001518152602001896040015163ffffffff1681526020018b81526020017f00000000000000000000000092240135b46fc1142da181f550ae8f595b85885473ffffffffffffffffffffffffffffffffffffffff16815260200161079a6101a4600160ff16610380020190565b8152895160209091015290506107af816110e7565b50508b5263ffffffff1660408b0152909350915061086c9050565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff05c63ffffffff87160161082f5760208701516040880151610815918791879187916105248d51611367565b63ffffffff1660408b015260208a0152909250905061086c565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02963ffffffff87160161086c57610866858561145d565b90925090505b60006108e6886040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b61016089015163ffffffff85811660408084019190915285821660e0909301929092526020830180518083168086526004909101831682526060808e01919091529051821660808d015291830151811660a08c0152908201511660c08a0152905061094f610408565b9a9950505050505050505050565b610160830151600090601f601585901c169082908260208110610982576109826127c5565b60200201519050601f601086901c16600061099c8761159c565b905082810163fffffffc166000610524905060006109bf8b600001518484611001565b905060007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd063ffffffff8b16016109f7575080610a93565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc863ffffffff8b1601610a615760008c61016001518763ffffffff1660208110610a4357610a436127c5565b60200201519050610a558585836115b3565b8d525060019050610a93565b6040517fecf79d0d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610b0d8d6040805160808101825260008082526020820181905291810182905260608101919091526040518060800160405280836060015163ffffffff168152602001836080015163ffffffff1681526020018360a0015163ffffffff1681526020018360c0015163ffffffff168152509050919050565b9050610b22818e610160015189856001611655565b610b5f8d82805163ffffffff9081166060808501919091526020830151821660808501526040830151821660a0850152909101511660c090910152565b610b67610408565b9d9c50505050505050505050505050565b604081015160a0820151600090819063ffffffff1660021480610ba557508360a0015163ffffffff166003145b15610bfe57608084015184516020808201519087015160a088015163f0000000909216630ffffffc600295861b161793610bf8939263ffffffff1614610bec57601f610bef565b60005b60ff168461172a565b50610ffa565b60808401516020808601516000928392601f601083901c8116939260151c16908110610c2c57610c2c6127c5565b602002015160a0880151909350819063ffffffff161580610c5757508760a0015163ffffffff16601c145b15610c985787602001518263ffffffff1660208110610c7857610c786127c5565b60200201519250600b886080015163ffffffff16901c601f169050610d77565b60208860a0015163ffffffff161015610d12578760a0015163ffffffff16600c1480610cce57508760a0015163ffffffff16600d145b80610ce357508760a0015163ffffffff16600e145b15610cf857876080015161ffff169250610d77565b610d0b886080015161ffff1660106117fd565b9250610d77565b60288860a0015163ffffffff16101580610d3657508760a0015163ffffffff166022145b80610d4b57508760a0015163ffffffff166026145b15610d775787602001518263ffffffff1660208110610d6c57610d6c6127c5565b602002015192508190505b60048860a0015163ffffffff1610158015610d9c575060088860a0015163ffffffff16105b80610db157508760a0015163ffffffff166001145b15610ddd57610dd4886000015189602001518a60a001518b608001518689611870565b50505050610ffa565b600063ffffffff9050600060208a60a0015163ffffffff1610610e4d57610e0d8a6080015161ffff1660106117fd565b8601955060008663fffffffc169050610e2f8b60400151828d60600151611001565b915060288b60a0015163ffffffff1610610e4b57809250600093505b505b6000610e698b608001518c60a001518d60c001518a8a87611ab3565b63ffffffff1690508a60a0015163ffffffff166000148015610e96575060088b60c0015163ffffffff1610155b8015610eac5750601c8b60c0015163ffffffff16105b15610fb2578a60c0015163ffffffff1660081480610ed457508a60c0015163ffffffff166009145b15610f1357610f078b600001518c602001518d60c0015163ffffffff16600814610efe5786610f01565b60005b8a61172a565b50505050505050610ffa565b8a60c0015163ffffffff16600a03610f40578a5160208c0151610f079190868a63ffffffff8b1615611655565b8a60c0015163ffffffff16600b03610f6e578a5160208c0151610f079190868a63ffffffff8b161515611655565b60108b60c0015163ffffffff1610158015610f935750601c8b60c0015163ffffffff16105b15610fb257610f078b600001518c602001518d60c001518a8a89612121565b8263ffffffff1663ffffffff14610fdc57610fd2838c60600151836115b3565b9950600198508297505b610ff28b600001518c6020015186846001611655565b505050505050505b9193909250565b60008061100f8585856123da565b90925090508061104b576040517f8e77b2b700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b509392505050565b6000808284610fff81161561106d57610fff811661100003015b8663ffffffff166000036110d95784935090810190636000000063ffffffff831611806110a557508463ffffffff168263ffffffff16105b806110bb57508563ffffffff168163ffffffff16105b156110d4575063ffffffff92506016915083905061057a565b6110dd565b8693505b5093509350939050565b610100810151608082015182516000928392918390819063ffffffff161561135e57865163ffffffff167ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb01611318576000876020015163fffffffc169050600061115c896101000151838b60e00151611001565b60608a015190915060001a6001036111de576111d889606001518a60a0015160408051600093845233602052918152606090922091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790565b60608a01525b6000808a60c0015173ffffffffffffffffffffffffffffffffffffffff1663e03110e18c606001518d608001516040518363ffffffff1660e01b815260040161123792919091825263ffffffff16602082015260400190565b6040805180830381865afa158015611253573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061127791906127f4565b60208d015160408e0151929450909250906003821660048190038481101561129d578094505b50838210156112aa578193505b8460088502610100031c9450846008828660040303021b9450600180600883600403021b036001806008878560040303021b039150811981169050858119881617965050506112fe868e60e00151876115b3565b929b5050509689019695506001945091925061135e915050565b865163ffffffff167ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd01611352578660400151955061135e565b63ffffffff9550600994505b91939550919395565b600080858563ffffffff8b1660011480611387575063ffffffff8b166002145b80611398575063ffffffff8b166004145b156113a55788935061144f565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa63ffffffff8c16016114435760006113e5868c63fffffffc1689611001565b90508860038c166004038b8110156113fb57809b505b8b965086900360089081029290921c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600193880293841b0116911b1791506000905061144f565b63ffffffff9350600992505b975097509750979350505050565b60008063ffffffff83166001036114f95763ffffffff84161580611487575063ffffffff84166001145b80611498575063ffffffff84166002145b806114a9575063ffffffff84166005145b806114ba575063ffffffff84166003145b806114cb575063ffffffff84166006145b806114dc575063ffffffff84166004145b156114ea5760009150611595565b5063ffffffff90506009611595565b8263ffffffff1660030361158a5763ffffffff84161580611520575063ffffffff84166005145b80611531575063ffffffff84166003145b1561153f5760009150611595565b63ffffffff84166001148061155a575063ffffffff84166002145b8061156b575063ffffffff84166006145b8061157c575063ffffffff84166004145b156114ea5760019150611595565b5063ffffffff905060165b9250929050565b60006115ad8261ffff1660106117fd565b92915050565b60006115be83612485565b60038416156115cc57600080fd5b6020830192601f8516601c0360031b83811b913563ffffffff90911b1916178460051c60005b601b81101561164a5760208601953582821c600116801561161a576001811461162f57611640565b60008581526020839052604090209450611640565b600082815260208690526040902094505b50506001016115f2565b509095945050505050565b60208363ffffffff16106116ca576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f76616c696420726567697374657200000000000000000000000000000000000060448201526064015b60405180910390fd5b63ffffffff8316158015906116dc5750805b1561170b5781848463ffffffff16602081106116fa576116fa6127c5565b63ffffffff90921660209290920201525b5050505060208101805163ffffffff8082169093526004019091169052565b836000015160040163ffffffff16846020015163ffffffff16146117aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6a756d7020696e2064656c617920736c6f74000000000000000000000000000060448201526064016116c1565b835160208501805163ffffffff90811687528381169091528316156117f65780600801848463ffffffff16602081106117e5576117e56127c5565b63ffffffff90921660209290920201525b5050505050565b600063ffffffff8381167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80850183169190911c821615159160016020869003821681901b830191861691821b92911b018261185a57600061185c565b815b90861663ffffffff16179250505092915050565b6000866000015160040163ffffffff16876020015163ffffffff16146118f2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6272616e636820696e2064656c617920736c6f7400000000000000000000000060448201526064016116c1565b8463ffffffff166004148061190d57508463ffffffff166005145b15611984576000868463ffffffff166020811061192c5761192c6127c5565b602002015190508063ffffffff168363ffffffff1614801561195457508563ffffffff166004145b8061197c57508063ffffffff168363ffffffff161415801561197c57508563ffffffff166005145b915050611a56565b8463ffffffff166006036119a15760008260030b13159050611a56565b8463ffffffff166007036119bd5760008260030b139050611a56565b8463ffffffff16600103611a5657601f601085901c1660008190036119e65760008360030b1291505b8063ffffffff16601003611a1057875160080163ffffffff166103e08801526000600384900b1291505b8063ffffffff16600103611a295760008360030b121591505b8063ffffffff16601103611a5457875160080163ffffffff166103e08801526000600384900b121591505b505b8651602088015163ffffffff1688528115611a97576002611a7c8661ffff1660106117fd565b63ffffffff90811690911b8201600401166020890152611aa9565b60208801805160040163ffffffff1690525b5050505050505050565b600063ffffffff86161580611ae0575060088663ffffffff1610158015611ae05750600f8663ffffffff16105b15611ee0578560088114611b235760098114611b2c57600a8114611b3557600b8114611b3e57600c8114611b4757600d8114611b5057600e8114611b5957611b5e565b60209550611b5e565b60219550611b5e565b602a9550611b5e565b602b9550611b5e565b60249550611b5e565b60259550611b5e565b602695505b508463ffffffff16600003611b83575063ffffffff8216601f600688901c161b612117565b8463ffffffff16600203611ba7575063ffffffff8216601f600688901c161c612117565b8463ffffffff16600303611bdb57601f600688901c16611bd363ffffffff8516821c60208390036117fd565b915050612117565b8463ffffffff16600403611bfb575063ffffffff8216601f84161b612117565b8463ffffffff16600603611c1b575063ffffffff8216601f84161c612117565b8463ffffffff16600703611c4357601f8416611bd363ffffffff8516821c60208390036117fd565b8463ffffffff16600803611c58575082612117565b8463ffffffff16600903611c6d575082612117565b8463ffffffff16600a03611c82575082612117565b8463ffffffff16600b03611c97575082612117565b8463ffffffff16600c03611cac575082612117565b8463ffffffff16600f03611cc1575082612117565b8463ffffffff16601003611cd6575082612117565b8463ffffffff16601103611ceb575082612117565b8463ffffffff16601203611d00575082612117565b8463ffffffff16601303611d15575082612117565b8463ffffffff16601803611d2a575082612117565b8463ffffffff16601903611d3f575082612117565b8463ffffffff16601a03611d54575082612117565b8463ffffffff16601b03611d69575082612117565b8463ffffffff16602003611d805750828201612117565b8463ffffffff16602103611d975750828201612117565b8463ffffffff16602203611dae5750818303612117565b8463ffffffff16602303611dc55750818303612117565b8463ffffffff16602403611ddc5750828216612117565b8463ffffffff16602503611df35750828217612117565b8463ffffffff16602603611e0a5750828218612117565b8463ffffffff16602703611e22575082821719612117565b8463ffffffff16602a03611e51578260030b8460030b12611e44576000611e47565b60015b60ff169050612117565b8463ffffffff16602b03611e79578263ffffffff168463ffffffff1610611e44576000611e47565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f696e76616c696420696e737472756374696f6e0000000000000000000000000060448201526064016116c1565b611e79565b8563ffffffff16601c03611f60578463ffffffff16600203611f055750828202612117565b8463ffffffff1660201480611f2057508463ffffffff166021145b15611edb578463ffffffff16602003611f37579219925b60005b6380000000851615611f59576401fffffffe600195861b169401611f3a565b9050612117565b8563ffffffff16600f03611f81575065ffffffff0000601083901b16612117565b8563ffffffff16602003611f9c57611f59848360018061251e565b8563ffffffff16602103611fb857611f5984836002600161251e565b8563ffffffff16602203611fe6575063ffffffff60086003851602811681811b198416918316901b17612117565b8563ffffffff1660230361200257611f5984836004600161251e565b8563ffffffff1660240361201e57611f5984836001600061251e565b8563ffffffff1660250361203a57611f5984836002600061251e565b8563ffffffff1660260361206b575063ffffffff60086003851602601803811681811c198416918316901c17612117565b8563ffffffff1660280361208657611f598483600186612566565b8563ffffffff166029036120a157611f598483600286612566565b8563ffffffff16602a036120cf575063ffffffff60086003851602811681811c198316918416901c17612117565b8563ffffffff16602b036120ea57611f598483600486612566565b8563ffffffff16602e03611e79575063ffffffff60086003851602601803811681811b198316918416901b175b9695505050505050565b60008463ffffffff1660100361213c57506060860151612382565b8463ffffffff1660110361215b5763ffffffff84166060880152612382565b8463ffffffff1660120361217457506040860151612382565b8463ffffffff166013036121935763ffffffff84166040880152612382565b8463ffffffff166018036121c75763ffffffff600385810b9085900b02602081901c821660608a0152166040880152612382565b8463ffffffff166019036121f85763ffffffff84811681851602602081901c821660608a0152166040880152612382565b8463ffffffff16601a036122bb578260030b600003612273576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d4950533a206469766973696f6e206279207a65726f0000000000000000000060448201526064016116c1565b8260030b8460030b8161228857612288612818565b0763ffffffff166060880152600383810b9085900b816122aa576122aa612818565b0563ffffffff166040880152612382565b8463ffffffff16601b03612382578263ffffffff16600003612339576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d4950533a206469766973696f6e206279207a65726f0000000000000000000060448201526064016116c1565b8263ffffffff168463ffffffff168161235457612354612818565b0663ffffffff90811660608901528381169085168161237557612375612818565b0463ffffffff1660408801525b63ffffffff8216156123b85780868363ffffffff16602081106123a7576123a76127c5565b63ffffffff90921660209290920201525b50505060208401805163ffffffff808216909652600401909416909352505050565b6000806123e683612485565b60038416156123f457600080fd5b6020830192358460051c8160005b601b81101561245a5760208701963583821c600116801561242a576001811461243f57612450565b60008481526020839052604090209350612450565b600082815260208590526040902093505b5050600101612402565b508714925050811561247c57601f8516601c0360031b81901c63ffffffff1692505b50935093915050565b36610380820181101561251a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f636865636b207468617420746865726520697320656e6f7567682063616c6c6460448201527f617461000000000000000000000000000000000000000000000000000000000060648201526084016116c1565b5050565b60008060008061252e888761259a565b925092509250828263ffffffff168863ffffffff16901c169350841561255b5761255884826117fd565b93505b505050949350505050565b6000806000612575878661259a565b5063ffffffff868316811691811691821b9216901b1987161792505050949350505050565b600080806407fffffff8600385901b16816125b6826020612847565b63ffffffff9081161c905060006125ce600188612847565b198816600316905060006125e3886004612847565b9050888216600060036125f68385612847565b959c63ffffffff909616901b9a50949850929650505050505050565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810191909152610160810161267861267d565b905290565b6040518061040001604052806020906020820280368337509192915050565b600060208083528351808285015260005b818110156126c9578581018301518582016040015282016126ad565b818111156126db576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60008083601f84011261272157600080fd5b50813567ffffffffffffffff81111561273957600080fd5b60208301915083602082850101111561159557600080fd5b60008060008060006060868803121561276957600080fd5b853567ffffffffffffffff8082111561278157600080fd5b61278d89838a0161270f565b909750955060208801359150808211156127a657600080fd5b506127b38882890161270f565b96999598509660400135949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000806040838503121561280757600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600063ffffffff8381169083168181101561288b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b03939250505056fea164736f6c634300080f000a") +var oracleCode = common.FromHex("0x6080604052600436106101d85760003560e01c80639d53a64811610102578063ddcd58de11610095578063ec5efcbc11610064578063ec5efcbc14610681578063f3f480d9146106a1578063faf37bc7146106d4578063fef2b4ed146106e757600080fd5b8063ddcd58de146105d4578063e03110e11461060c578063e159261114610641578063ea7139501461066157600080fd5b8063b5e7154c116100d1578063b5e7154c14610555578063d18534b51461056c578063da35c6641461058c578063dd24f9bf146105a157600080fd5b80639d53a6481461048e5780639d7e8769146104dd578063b2e67ba8146104fd578063b4801e611461053557600080fd5b806361238bde1161017a5780637ac54767116101495780637ac54767146103ca5780638542cf50146103ea578063882856ef146104355780638dc4be111461046e57600080fd5b806361238bde1461031e5780636551927b146103565780637051472e1461038e5780637917de1d146103aa57600080fd5b80633909af5c116101b65780633909af5c146102715780634d52b4c91461029357806352f0f3ad146102a857806354fd4d50146102c857600080fd5b8063013cf08b146101dd5780630359a5631461022e5780632055b36b1461025c575b600080fd5b3480156101e957600080fd5b506101fd6101f8366004612e29565b610714565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152015b60405180910390f35b34801561023a57600080fd5b5061024e610249366004612e6b565b610759565b604051908152602001610225565b34801561026857600080fd5b5061024e601081565b34801561027d57600080fd5b5061029161028c366004613073565b610891565b005b34801561029f57600080fd5b5061024e610ae8565b3480156102b457600080fd5b5061024e6102c336600461315f565b610b03565b3480156102d457600080fd5b506103116040518060400160405280600581526020017f312e312e3200000000000000000000000000000000000000000000000000000081525081565b60405161022591906131c6565b34801561032a57600080fd5b5061024e610339366004613217565b600160209081526000928352604080842090915290825290205481565b34801561036257600080fd5b5061024e610371366004612e6b565b601560209081526000928352604080842090915290825290205481565b34801561039a57600080fd5b5061024e6703782dace9d9000081565b3480156103b657600080fd5b506102916103c536600461327b565b610bd9565b3480156103d657600080fd5b5061024e6103e5366004612e29565b6110dc565b3480156103f657600080fd5b50610425610405366004613217565b600260209081526000928352604080842090915290825290205460ff1681565b6040519015158152602001610225565b34801561044157600080fd5b50610455610450366004613317565b6110f3565b60405167ffffffffffffffff9091168152602001610225565b34801561047a57600080fd5b5061029161048936600461334a565b61114d565b34801561049a57600080fd5b5061024e6104a9366004612e6b565b73ffffffffffffffffffffffffffffffffffffffff9091166000908152601860209081526040808320938352929052205490565b3480156104e957600080fd5b506102916104f8366004613396565b611248565b34801561050957600080fd5b5061024e610518366004612e6b565b601760209081526000928352604080842090915290825290205481565b34801561054157600080fd5b5061024e610550366004613317565b6113ff565b34801561056157600080fd5b5061024e620186a081565b34801561057857600080fd5b50610291610587366004613073565b611431565b34801561059857600080fd5b5060135461024e565b3480156105ad57600080fd5b507f000000000000000000000000000000000000000000000000000000000001ec3061024e565b3480156105e057600080fd5b5061024e6105ef366004612e6b565b601660209081526000928352604080842090915290825290205481565b34801561061857600080fd5b5061062c610627366004613217565b611840565b60408051928352602083019190915201610225565b34801561064d57600080fd5b5061029161065c36600461334a565b611931565b34801561066d57600080fd5b5061029161067c366004613422565b611a39565b34801561068d57600080fd5b5061029161069c366004613491565b611b98565b3480156106ad57600080fd5b507f000000000000000000000000000000000000000000000000000000000001518061024e565b6102916106e2366004613519565b611d1e565b3480156106f357600080fd5b5061024e610702366004612e29565b60006020819052908152604090205481565b6013818154811061072457600080fd5b60009182526020909120600290910201805460019091015473ffffffffffffffffffffffffffffffffffffffff909116915082565b73ffffffffffffffffffffffffffffffffffffffff82166000908152601560209081526040808320848452909152812054819061079c9060601c63ffffffff1690565b63ffffffff16905060005b6010811015610889578160011660010361082f5773ffffffffffffffffffffffffffffffffffffffff85166000908152601460209081526040808320878452909152902081601081106107fc576107fc613555565b01546040805160208101929092528101849052606001604051602081830303815290604052805190602001209250610870565b826003826010811061084357610843613555565b01546040805160208101939093528201526060016040516020818303038152906040528051906020012092505b60019190911c9080610881816135b3565b9150506107a7565b505092915050565b600061089d8a8a610759565b90506108c086868360208b01356108bb6108b68d6135eb565b611fea565b61202a565b80156108de57506108de83838360208801356108bb6108b68a6135eb565b610914576040517f09bde33900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b86604001358860405160200161092a91906136ba565b6040516020818303038152906040528051906020012014610977576040517f1968a90200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83602001358760200135600161098d91906136f8565b146109c4576040517f9a3b119900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610a0c886109d28680613710565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061208b92505050565b610a15886121e6565b836040013588604051602001610a2b91906136ba565b6040516020818303038152906040528051906020012003610a78576040517f9843145b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8a1660009081526015602090815260408083208c8452909152902080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166001179055610adc8a8a3361298e565b50505050505050505050565b6001610af660106002613897565b610b0091906138a3565b81565b6000610b0f8686612a47565b9050610b1c8360086136f8565b82101580610b2a5750602083115b15610b61576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000602081815260c085901b82526008959095528251828252600286526040808320858452875280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091558484528752808320948352938652838220558181529384905292205592915050565b60608115610bf257610beb8686612af4565b9050610c2c565b85858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509293505050505b3360009081526014602090815260408083208b845290915280822081516102008101928390529160109082845b815481526020019060010190808311610c5957505050505090506000601560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008b81526020019081526020016000205490506000610cda8260601c63ffffffff1690565b63ffffffff169050333214610d1b576040517fba092d1600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610d2b8260801c63ffffffff1690565b63ffffffff16600003610d6a576040517f87138d5c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610d748260c01c90565b67ffffffffffffffff1615610db5576040517f475a253500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b898114610dee576040517f60f95d5a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dfb89898d8886612b6d565b83516020850160888204881415608883061715610e20576307b1daf16000526004601cfd5b60405160c8810160405260005b83811015610ed0578083018051835260208101516020840152604081015160408401526060810151606084015260808101516080840152508460888301526088810460051b8b013560a883015260c882206001860195508560005b610200811015610ec5576001821615610ea55782818b0152610ec5565b8981015160009081526020938452604090209260019290921c9101610e88565b505050608801610e2d565b50505050600160106002610ee49190613897565b610eee91906138a3565b811115610f27576040517f6229572300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610f9c610f3a8360401c63ffffffff1690565b610f4a9063ffffffff168a6136f8565b60401b7fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff606084901b167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8516171790565b915084156110295777ffffffffffffffffffffffffffffffffffffffffffffffff82164260c01b179150610fd68260801c63ffffffff1690565b63ffffffff16610fec8360401c63ffffffff1690565b63ffffffff1614611029576040517f7b1dafd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3360009081526014602090815260408083208e8452909152902061104f90846010612d9f565b503360008181526018602090815260408083208f8452825280832080546001810182559084528284206004820401805460039092166008026101000a67ffffffffffffffff818102199093164390931602919091179055838352601582528083208f8452909152812084905560609190911b81523690601437366014016000a05050505050505050505050565b600381601081106110ec57600080fd5b0154905081565b6018602052826000526040600020602052816000526040600020818154811061111b57600080fd5b906000526020600020906004918282040191900660080292509250509054906101000a900467ffffffffffffffff1681565b60443560008060088301861061116b5763fe2549876000526004601cfd5b60c083901b60805260888386823786600882030151915060206000858360025afa90508061119857600080fd5b50600080517effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0400000000000000000000000000000000000000000000000000000000000000178082526002602090815260408084208a8552825280842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091558385528252808420998452988152888320939093558152908190529490942055505050565b600080603087600037602060006030600060025afa806112705763f91129696000526004601cfd5b6000517effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f010000000000000000000000000000000000000000000000000000000000000017608081815260a08c905260c08b905260308a60e037603088609083013760008060c083600a5afa9250826112f2576309bde3396000526004601cfd5b602886106113085763fe2549876000526004601cfd5b6000602882015278200000000000000000000000000000000000000000000000008152600881018b905285810151935060308a8237603081019b909b52505060509098207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0500000000000000000000000000000000000000000000000000000000000000176000818152600260209081526040808320868452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845282528083209583529481528482209a909a559081528089529190912096909655505050505050565b6014602052826000526040600020602052816000526040600020816010811061142757600080fd5b0154925083915050565b73ffffffffffffffffffffffffffffffffffffffff891660009081526015602090815260408083208b845290915290205467ffffffffffffffff8116156114a4576040517fc334f06900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6114ae8160c01c90565b67ffffffffffffffff166000036114f1576040517f55d4cbf900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000001518061151c8260c01c90565b6115309067ffffffffffffffff16426138a3565b11611567576040517f55d4cbf900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006115738b8b610759565b905061158c87878360208c01356108bb6108b68e6135eb565b80156115aa57506115aa84848360208901356108bb6108b68b6135eb565b6115e0576040517f09bde33900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8760400135896040516020016115f691906136ba565b6040516020818303038152906040528051906020012014611643576040517f1968a90200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84602001358860200135600161165991906136f8565b14158061168b575060016116738360601c63ffffffff1690565b61167d91906138ba565b63ffffffff16856020013514155b156116c2576040517f9a3b119900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6116d0896109d28780613710565b6116d9896121e6565b60006116e48a612cc0565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0200000000000000000000000000000000000000000000000000000000000000179050600061173b8460a01c63ffffffff1690565b67ffffffffffffffff169050600160026000848152602001908152602001600020600083815260200190815260200160002060006101000a81548160ff021916908315150217905550601760008e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008d8152602001908152602001600020546001600084815260200190815260200160002060008381526020019081526020016000208190555061180d8460801c63ffffffff1690565b600083815260208190526040902063ffffffff9190911690556118318d8d8161298e565b50505050505050505050505050565b6000828152600260209081526040808320848452909152812054819060ff166118c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f7072652d696d616765206d757374206578697374000000000000000000000000604482015260640160405180910390fd5b50600083815260208181526040909120546118e58160086136f8565b6118f08560206136f8565b1061190e57836119018260086136f8565b61190b91906138a3565b91505b506000938452600160209081526040808620948652939052919092205492909150565b60443560008060088301861061194f5763fe2549876000526004601cfd5b60c083901b6080526088838682378087017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80151908490207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f02000000000000000000000000000000000000000000000000000000000000001760008181526002602090815260408083208b8452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915584845282528083209a83529981528982209390935590815290819052959095209190915550505050565b60008060008060808860601b81528760c01b6014820152858782601c0137601c860181207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0600000000000000000000000000000000000000000000000000000000000000179350604088026260216001603f5a021015611ac35763dd629f866000526004601cfd5b6000808783601c018c5afa94503d6001019150600882018a10611aee5763fe2549876000526004601cfd5b60c082901b81526008018481533d6000600183013e89017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8015160008481526002602090815260408083208d8452825280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915587845282528083209c83529b81528b8220929092559384528390529790912096909655505050505050565b6000611ba48686610759565b9050611bbd83838360208801356108bb6108b68a6135eb565b611bf3576040517f09bde33900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602084013515611c2f576040517f9a3b119900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c37612ddd565b611c45816109d28780613710565b611c4e816121e6565b846040013581604051602001611c6491906136ba565b6040516020818303038152906040528051906020012003611cb1576040517f9843145b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff87166000908152601560209081526040808320898452909152902080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000166001179055611d1587873361298e565b50505050505050565b6703782dace9d90000341015611d60576040517fe92c469f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b333214611d99576040517fba092d1600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611da48160086138df565b63ffffffff168263ffffffff1610611de8576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000001ec308163ffffffff161015611e48576040517f7b1dafd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152601560209081526040808320868452909152902054611e738160801c63ffffffff1690565b63ffffffff1615611eb0576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608082901b7fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff60a085901b167fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff83161717336000818152601560209081526040808320898452825280832094909455835180850185528381528082018981526013805460018101825590855291517f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090600290930292830180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909216919091179055517f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a0919091015591815260168252828120968152959052909320349055505050565b600081600001518260200151836040015160405160200161200d93929190613907565b604051602081830303815290604052805190602001209050919050565b60008160005b601081101561207e578060051b880135600186831c16600181146120635760008481526020839052604090209350612074565b600082815260208590526040902093505b5050600101612030565b5090931495945050505050565b608881511461209957600080fd5b602081016020830161211a565b8260031b8201518060001a8160011a60081b178160021a60101b8260031a60181b17178160041a60201b8260051a60281b178260061a60301b8360071a60381b1717179050612114816120ff868560059190911b015190565b1867ffffffffffffffff16600586901b840152565b50505050565b612126600083836120a6565b612132600183836120a6565b61213e600283836120a6565b61214a600383836120a6565b612156600483836120a6565b612162600583836120a6565b61216e600683836120a6565b61217a600783836120a6565b612186600883836120a6565b612192600983836120a6565b61219e600a83836120a6565b6121aa600b83836120a6565b6121b6600c83836120a6565b6121c2600d83836120a6565b6121ce600e83836120a6565b6121da600f83836120a6565b612114601083836120a6565b6040805178010000000000008082800000000000808a8000000080008000602082015279808b00000000800000018000000080008081800000000000800991810191909152788a00000000000000880000000080008009000000008000000a60608201527b8000808b800000000000008b8000000000008089800000000000800360808201527f80000000000080028000000000000080000000000000800a800000008000000a60a08201527f800000008000808180000000000080800000000080000001800000008000800860c082015260009060e0016040516020818303038152906040529050602082016020820161286e565b6102808101516101e082015161014083015160a0840151845118189118186102a082015161020083015161016084015160c0850151602086015118189118186102c083015161022084015161018085015160e0860151604087015118189118186102e08401516102408501516101a0860151610100870151606088015118189118186103008501516102608601516101c0870151610120880151608089015118189118188084603f1c6123998660011b67ffffffffffffffff1690565b18188584603f1c6123b48660011b67ffffffffffffffff1690565b18188584603f1c6123cf8660011b67ffffffffffffffff1690565b181895508483603f1c6123ec8560011b67ffffffffffffffff1690565b181894508387603f1c6124098960011b67ffffffffffffffff1690565b60208b01518b51861867ffffffffffffffff168c5291189190911897508118600181901b603f9190911c18935060c08801518118601481901c602c9190911b1867ffffffffffffffff1660208901526101208801518718602c81901c60149190911b1867ffffffffffffffff1660c08901526102c08801518618600381901c603d9190911b1867ffffffffffffffff166101208901526101c08801518718601981901c60279190911b1867ffffffffffffffff166102c08901526102808801518218602e81901c60129190911b1867ffffffffffffffff166101c089015260408801518618600281901c603e9190911b1867ffffffffffffffff166102808901526101808801518618601581901c602b9190911b1867ffffffffffffffff1660408901526101a08801518518602781901c60199190911b1867ffffffffffffffff166101808901526102608801518718603881901c60089190911b1867ffffffffffffffff166101a08901526102e08801518518600881901c60389190911b1867ffffffffffffffff166102608901526101e08801518218601781901c60299190911b1867ffffffffffffffff166102e089015260808801518718602581901c601b9190911b1867ffffffffffffffff166101e08901526103008801518718603281901c600e9190911b1867ffffffffffffffff1660808901526102a08801518118603e81901c60029190911b1867ffffffffffffffff166103008901526101008801518518600981901c60379190911b1867ffffffffffffffff166102a08901526102008801518118601381901c602d9190911b1867ffffffffffffffff1661010089015260a08801518218601c81901c60249190911b1867ffffffffffffffff1661020089015260608801518518602481901c601c9190911b1867ffffffffffffffff1660a08901526102408801518518602b81901c60159190911b1867ffffffffffffffff1660608901526102208801518618603181901c600f9190911b1867ffffffffffffffff166102408901526101608801518118603681901c600a9190911b1867ffffffffffffffff166102208901525060e08701518518603a81901c60069190911b1867ffffffffffffffff166101608801526101408701518118603d81901c60039190911b1867ffffffffffffffff1660e0880152505067ffffffffffffffff81166101408601525b5050505050565b600582811b8201805160018501831b8401805160028701851b8601805160038901871b8801805160048b0190981b8901805167ffffffffffffffff861985168918811690995283198a16861889169096528819861683188816909352841986168818871690528419831684189095169052919391929190611d15565b612808600082612781565b612813600582612781565b61281e600a82612781565b612829600f82612781565b612834601482612781565b50565b612840816122dc565b612849816127fd565b600383901b820151815160c09190911c9061211490821867ffffffffffffffff168352565b61287a60008284612837565b61288660018284612837565b61289260028284612837565b61289e60038284612837565b6128aa60048284612837565b6128b660058284612837565b6128c260068284612837565b6128ce60078284612837565b6128da60088284612837565b6128e660098284612837565b6128f2600a8284612837565b6128fe600b8284612837565b61290a600c8284612837565b612916600d8284612837565b612922600e8284612837565b61292e600f8284612837565b61293a60108284612837565b61294660118284612837565b61295260128284612837565b61295e60138284612837565b61296a60148284612837565b61297660158284612837565b61298260168284612837565b61211460178284612837565b73ffffffffffffffffffffffffffffffffffffffff83811660009081526016602090815260408083208684529091528082208054908390559051909284169083908381818185875af1925050503d8060008114612a07576040519150601f19603f3d011682016040523d82523d6000602084013e612a0c565b606091505b505090508061277a576040517f83e6cc6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f01000000000000000000000000000000000000000000000000000000000000007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831617612aed818360408051600093845233602052918152606090922091527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01000000000000000000000000000000000000000000000000000000000000001790565b9392505050565b6060604051905081602082018181018286833760888306808015612b3d5760888290038501848101848103803687375060806001820353506001845160001a1784538652612b54565b608836843760018353608060878401536088850186525b5050505050601f19603f82510116810160405292915050565b6000612b7f8260a01c63ffffffff1690565b67ffffffffffffffff1690506000612b9d8360801c63ffffffff1690565b63ffffffff1690506000612bb78460401c63ffffffff1690565b63ffffffff169050600883108015612bcd575080155b15612c015760c082901b6000908152883560085283513382526017602090815260408084208a855290915290912055612cb6565b60088310158015612c1f575080612c196008856138a3565b93508310155b8015612c335750612c3087826136f8565b83105b15612cb6576000612c4482856138a3565b905087612c528260206136f8565b10158015612c5e575085155b15612c95576040517ffe25498700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3360009081526017602090815260408083208a845290915290209089013590555b5050505050505050565b6000612d43565b66ff00ff00ff00ff8160081c1667ff00ff00ff00ff00612cf18360081b67ffffffffffffffff1690565b1617905065ffff0000ffff8160101c1667ffff0000ffff0000612d1e8360101b67ffffffffffffffff1690565b1617905060008160201c612d3c8360201b67ffffffffffffffff1690565b1792915050565b60808201516020830190612d5b90612cc7565b612cc7565b6040820151612d6990612cc7565b60401b17612d81612d5660018460059190911b015190565b825160809190911b90612d9390612cc7565b60c01b17179392505050565b8260108101928215612dcd579160200282015b82811115612dcd578251825591602001919060010190612db2565b50612dd9929150612df5565b5090565b6040518060200160405280612df0612e0a565b905290565b5b80821115612dd95760008155600101612df6565b6040518061032001604052806019906020820280368337509192915050565b600060208284031215612e3b57600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114612e6657600080fd5b919050565b60008060408385031215612e7e57600080fd5b612e8783612e42565b946020939093013593505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610320810167ffffffffffffffff81118282101715612ee857612ee8612e95565b60405290565b6040516060810167ffffffffffffffff81118282101715612ee857612ee8612e95565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715612f5857612f58612e95565b604052919050565b803567ffffffffffffffff81168114612e6657600080fd5b6000610320808385031215612f8c57600080fd5b604051602080820182811067ffffffffffffffff82111715612fb057612fb0612e95565b806040525081935085601f860112612fc757600080fd5b612fcf612ec4565b928501928087851115612fe157600080fd5b865b8581101561300157612ff481612f60565b8352918301918301612fe3565b509092525091949350505050565b60006060828403121561302157600080fd5b50919050565b60008083601f84011261303957600080fd5b50813567ffffffffffffffff81111561305157600080fd5b6020830191508360208260051b850101111561306c57600080fd5b9250929050565b60008060008060008060008060006103e08a8c03121561309257600080fd5b61309b8a612e42565b985060208a013597506130b18b60408c01612f78565b96506103608a013567ffffffffffffffff808211156130cf57600080fd5b6130db8d838e0161300f565b97506103808c01359150808211156130f257600080fd5b6130fe8d838e01613027565b90975095506103a08c013591508082111561311857600080fd5b6131248d838e0161300f565b94506103c08c013591508082111561313b57600080fd5b506131488c828d01613027565b915080935050809150509295985092959850929598565b600080600080600060a0868803121561317757600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b60005b838110156131b557818101518382015260200161319d565b838111156121145750506000910152565b60208152600082518060208401526131e581604085016020870161319a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b6000806040838503121561322a57600080fd5b50508035926020909101359150565b60008083601f84011261324b57600080fd5b50813567ffffffffffffffff81111561326357600080fd5b60208301915083602082850101111561306c57600080fd5b600080600080600080600060a0888a03121561329657600080fd5b8735965060208801359550604088013567ffffffffffffffff808211156132bc57600080fd5b6132c88b838c01613239565b909750955060608a01359150808211156132e157600080fd5b506132ee8a828b01613027565b9094509250506080880135801515811461330757600080fd5b8091505092959891949750929550565b60008060006060848603121561332c57600080fd5b61333584612e42565b95602085013595506040909401359392505050565b60008060006040848603121561335f57600080fd5b83359250602084013567ffffffffffffffff81111561337d57600080fd5b61338986828701613239565b9497909650939450505050565b600080600080600080600060a0888a0312156133b157600080fd5b8735965060208801359550604088013567ffffffffffffffff808211156133d757600080fd5b6133e38b838c01613239565b909750955060608a01359150808211156133fc57600080fd5b506134098a828b01613239565b989b979a50959894979596608090950135949350505050565b60008060008060006080868803121561343a57600080fd5b8535945061344a60208701612e42565b935061345860408701612f60565b9250606086013567ffffffffffffffff81111561347457600080fd5b61348088828901613239565b969995985093965092949392505050565b6000806000806000608086880312156134a957600080fd5b6134b286612e42565b945060208601359350604086013567ffffffffffffffff808211156134d657600080fd5b6134e289838a0161300f565b945060608801359150808211156134f857600080fd5b5061348088828901613027565b803563ffffffff81168114612e6657600080fd5b60008060006060848603121561352e57600080fd5b8335925061353e60208501613505565b915061354c60408501613505565b90509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036135e4576135e4613584565b5060010190565b6000606082360312156135fd57600080fd5b613605612eee565b823567ffffffffffffffff8082111561361d57600080fd5b9084019036601f83011261363057600080fd5b813560208282111561364457613644612e95565b613674817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f85011601612f11565b9250818352368183860101111561368a57600080fd5b81818501828501376000918301810191909152908352848101359083015250604092830135928101929092525090565b81516103208201908260005b60198110156136ef57825167ffffffffffffffff168252602092830192909101906001016136c6565b50505092915050565b6000821982111561370b5761370b613584565b500190565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261374557600080fd5b83018035915067ffffffffffffffff82111561376057600080fd5b60200191503681900382131561306c57600080fd5b600181815b808511156137ce57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156137b4576137b4613584565b808516156137c157918102915b93841c939080029061377a565b509250929050565b6000826137e557506001613891565b816137f257506000613891565b816001811461380857600281146138125761382e565b6001915050613891565b60ff84111561382357613823613584565b50506001821b613891565b5060208310610133831016604e8410600b8410161715613851575081810a613891565b61385b8383613775565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561388d5761388d613584565b0290505b92915050565b6000612aed83836137d6565b6000828210156138b5576138b5613584565b500390565b600063ffffffff838116908316818110156138d7576138d7613584565b039392505050565b600063ffffffff8083168185168083038211156138fe576138fe613584565b01949350505050565b6000845161391981846020890161319a565b9190910192835250602082015260400191905056fea164736f6c634300080f000a") diff --git a/op-chain-ops/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go similarity index 95% rename from op-chain-ops/deployer/opcm/implementations.go rename to op-deployer/pkg/deployer/opcm/implementations.go index 8dd072eef246a..413452b1d3480 100644 --- a/op-chain-ops/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -18,12 +18,11 @@ type DeployImplementationsInput struct { DisputeGameFinalityDelaySeconds *big.Int MipsVersion *big.Int // Release version to set OPCM implementations for, of the format `op-contracts/vX.Y.Z`. - Release string + L1ContractsRelease string SuperchainConfigProxy common.Address ProtocolVersionsProxy common.Address UseInterop bool // if true, deploy Interop implementations - OpcmProxyOwner common.Address StandardVersionsToml string // contents of 'standard-versions-mainnet.toml' or 'standard-versions-sepolia.toml' file } @@ -32,8 +31,7 @@ func (input *DeployImplementationsInput) InputSet() bool { } type DeployImplementationsOutput struct { - OpcmProxy common.Address - OpcmImpl common.Address + Opcm common.Address DelayedWETHImpl common.Address OptimismPortalImpl common.Address PreimageOracleSingleton common.Address diff --git a/op-chain-ops/deployer/opcm/l2genesis.go b/op-deployer/pkg/deployer/opcm/l2genesis.go similarity index 100% rename from op-chain-ops/deployer/opcm/l2genesis.go rename to op-deployer/pkg/deployer/opcm/l2genesis.go diff --git a/op-deployer/pkg/deployer/opcm/mips.go b/op-deployer/pkg/deployer/opcm/mips.go new file mode 100644 index 0000000000000..6150b3088351d --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/mips.go @@ -0,0 +1,35 @@ +package opcm + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +type DeployMIPSInput struct { + MipsVersion uint64 + PreimageOracle common.Address +} + +func (input *DeployMIPSInput) InputSet() bool { + return true +} + +type DeployMIPSOutput struct { + MipsSingleton common.Address +} + +func (output *DeployMIPSOutput) CheckOutput(input common.Address) error { + return nil +} + +type DeployMIPSScript struct { + Run func(input, output common.Address) error +} + +func DeployMIPS( + host *script.Host, + input DeployMIPSInput, +) (DeployMIPSOutput, error) { + return RunBasicScript[DeployMIPSInput, DeployMIPSOutput](host, input, "DeployMIPS.s.sol", "DeployMIPS") +} diff --git a/op-deployer/pkg/deployer/opcm/mips_test.go b/op-deployer/pkg/deployer/opcm/mips_test.go new file mode 100644 index 0000000000000..848b463565258 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/mips_test.go @@ -0,0 +1,35 @@ +package opcm + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestDeployMIPS(t *testing.T) { + _, artifacts := testutil.LocalArtifacts(t) + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + testlog.Logger(t, log.LevelInfo), + common.Address{'D'}, + artifacts, + ) + require.NoError(t, err) + + input := DeployMIPSInput{ + MipsVersion: 1, + PreimageOracle: common.Address{0xab}, + } + + output, err := DeployMIPS(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.MipsSingleton) +} diff --git a/op-deployer/pkg/deployer/opcm/opchain.go b/op-deployer/pkg/deployer/opcm/opchain.go new file mode 100644 index 0000000000000..2a86645013371 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/opchain.go @@ -0,0 +1,148 @@ +package opcm + +import ( + _ "embed" + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum/go-ethereum/common" +) + +// PermissionedGameStartingAnchorRoots is a root of bytes32(hex"dead") for the permissioned game at block 0, +// and no root for the permissionless game. +var PermissionedGameStartingAnchorRoots = []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +} + +type DeployOPChainInputV160 struct { + OpChainProxyAdminOwner common.Address + SystemConfigOwner common.Address + Batcher common.Address + UnsafeBlockSigner common.Address + Proposer common.Address + Challenger common.Address + + BasefeeScalar uint32 + BlobBaseFeeScalar uint32 + L2ChainId *big.Int + Opcm common.Address + SaltMixer string + GasLimit uint64 + + DisputeGameType uint32 + DisputeAbsolutePrestate common.Hash + DisputeMaxGameDepth uint64 + DisputeSplitDepth uint64 + DisputeClockExtension uint64 + DisputeMaxClockDuration uint64 + AllowCustomDisputeParameters bool +} + +func (input *DeployOPChainInputV160) InputSet() bool { + return true +} + +func (input *DeployOPChainInputV160) StartingAnchorRoots() []byte { + return PermissionedGameStartingAnchorRoots +} + +type DeployOPChainInputIsthmus struct { + DeployOPChainInputV160 + SystemConfigFeeAdmin common.Address +} + +type DeployOPChainOutput struct { + OpChainProxyAdmin common.Address + AddressManager common.Address + L1ERC721BridgeProxy common.Address + SystemConfigProxy common.Address + OptimismMintableERC20FactoryProxy common.Address + L1StandardBridgeProxy common.Address + L1CrossDomainMessengerProxy common.Address + // Fault proof contracts below. + OptimismPortalProxy common.Address + DisputeGameFactoryProxy common.Address + AnchorStateRegistryProxy common.Address + AnchorStateRegistryImpl common.Address + FaultDisputeGame common.Address + PermissionedDisputeGame common.Address + DelayedWETHPermissionedGameProxy common.Address + DelayedWETHPermissionlessGameProxy common.Address +} + +func (output *DeployOPChainOutput) CheckOutput(input common.Address) error { + return nil +} + +type DeployOPChainScript struct { + Run func(input, output common.Address) error +} + +func DeployOPChainV160(host *script.Host, input DeployOPChainInputV160) (DeployOPChainOutput, error) { + return deployOPChain(host, input) +} + +func DeployOPChainIsthmus(host *script.Host, input DeployOPChainInputIsthmus) (DeployOPChainOutput, error) { + return deployOPChain(host, input) +} + +func deployOPChain[T any](host *script.Host, input T) (DeployOPChainOutput, error) { + return RunBasicScript[T, DeployOPChainOutput](host, input, "DeployOPChain.s.sol", "DeployOPChain") +} + +type ReadImplementationAddressesInput struct { + DeployOPChainOutput + Opcm common.Address + Release string +} + +type ReadImplementationAddressesOutput struct { + DelayedWETH common.Address + OptimismPortal common.Address + SystemConfig common.Address + L1CrossDomainMessenger common.Address + L1ERC721Bridge common.Address + L1StandardBridge common.Address + OptimismMintableERC20Factory common.Address + DisputeGameFactory common.Address + MipsSingleton common.Address + PreimageOracleSingleton common.Address +} + +type ReadImplementationAddressesScript struct { + Run func(input, output common.Address) error +} + +func ReadImplementationAddresses(host *script.Host, input ReadImplementationAddressesInput) (ReadImplementationAddressesOutput, error) { + var rio ReadImplementationAddressesOutput + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() + + cleanupInput, err := script.WithPrecompileAtAddress[*ReadImplementationAddressesInput](host, inputAddr, &input) + if err != nil { + return rio, fmt.Errorf("failed to insert ReadImplementationAddressesInput precompile: %w", err) + } + defer cleanupInput() + host.Label(inputAddr, "ReadImplementationAddressesInput") + + cleanupOutput, err := script.WithPrecompileAtAddress[*ReadImplementationAddressesOutput](host, outputAddr, &rio, + script.WithFieldSetter[*ReadImplementationAddressesOutput]) + if err != nil { + return rio, fmt.Errorf("failed to insert ReadImplementationAddressesOutput precompile: %w", err) + } + defer cleanupOutput() + host.Label(outputAddr, "ReadImplementationAddressesOutput") + + deployScript, cleanupDeploy, err := script.WithScript[ReadImplementationAddressesScript](host, "ReadImplementationAddresses.s.sol", "ReadImplementationAddresses") + if err != nil { + return rio, fmt.Errorf("failed to load ReadImplementationAddresses script: %w", err) + } + defer cleanupDeploy() + + if err := deployScript.Run(inputAddr, outputAddr); err != nil { + return rio, fmt.Errorf("failed to run ReadImplementationAddresses script: %w", err) + } + + return rio, nil +} diff --git a/op-deployer/pkg/deployer/opcm/opcm.go b/op-deployer/pkg/deployer/opcm/opcm.go new file mode 100644 index 0000000000000..26179271b6924 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/opcm.go @@ -0,0 +1,53 @@ +package opcm + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum/go-ethereum/common" +) + +type DeployOPCMInput struct { + SuperchainConfig common.Address + ProtocolVersions common.Address + L1ContractsRelease string + + AddressManagerBlueprint common.Address + ProxyBlueprint common.Address + ProxyAdminBlueprint common.Address + L1ChugSplashProxyBlueprint common.Address + ResolvedDelegateProxyBlueprint common.Address + AnchorStateRegistryBlueprint common.Address + PermissionedDisputeGame1Blueprint common.Address + PermissionedDisputeGame2Blueprint common.Address + + L1ERC721BridgeImpl common.Address + OptimismPortalImpl common.Address + SystemConfigImpl common.Address + OptimismMintableERC20FactoryImpl common.Address + L1CrossDomainMessengerImpl common.Address + L1StandardBridgeImpl common.Address + DisputeGameFactoryImpl common.Address + DelayedWETHImpl common.Address + MipsImpl common.Address +} + +type DeployOPCMOutput struct { + Opcm common.Address +} + +func DeployOPCM( + host *script.Host, + input DeployOPCMInput, +) (DeployOPCMOutput, error) { + out, err := RunBasicScript[DeployOPCMInput, DeployOPCMOutput](host, input, "DeployOPCM.s.sol", "DeployOPCM") + if err != nil { + return DeployOPCMOutput{}, fmt.Errorf("failed to deploy OPCM: %w", err) + } + + if err := host.RememberOnLabel("OPContractsManager", "OPContractsManager.sol", "OPContractsManager"); err != nil { + return DeployOPCMOutput{}, fmt.Errorf("failed to link OPContractsManager label: %w", err) + } + + return out, nil +} diff --git a/op-deployer/pkg/deployer/opcm/script.go b/op-deployer/pkg/deployer/opcm/script.go new file mode 100644 index 0000000000000..6a138f67cb8f8 --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/script.go @@ -0,0 +1,48 @@ +package opcm + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum/go-ethereum/common" +) + +type BasicScriptIO struct { + Run func(input, output common.Address) error +} + +func RunBasicScript[I any, O any]( + host *script.Host, + input I, + scriptFile string, + contractName string, +) (O, error) { + var output O + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() + + cleanupInput, err := script.WithPrecompileAtAddress[*I](host, inputAddr, &input) + if err != nil { + return output, fmt.Errorf("failed to insert input precompile: %w", err) + } + defer cleanupInput() + + cleanupOutput, err := script.WithPrecompileAtAddress[*O](host, outputAddr, &output, + script.WithFieldSetter[*O]) + if err != nil { + return output, fmt.Errorf("failed to insert output precompile: %w", err) + } + defer cleanupOutput() + + deployScript, cleanupDeploy, err := script.WithScript[BasicScriptIO](host, scriptFile, contractName) + if err != nil { + return output, fmt.Errorf("failed to load %s script: %w", scriptFile, err) + } + defer cleanupDeploy() + + if err := deployScript.Run(inputAddr, outputAddr); err != nil { + return output, fmt.Errorf("failed to run %s script: %w", scriptFile, err) + } + + return output, nil +} diff --git a/op-chain-ops/deployer/opcm/superchain.go b/op-deployer/pkg/deployer/opcm/superchain.go similarity index 62% rename from op-chain-ops/deployer/opcm/superchain.go rename to op-deployer/pkg/deployer/opcm/superchain.go index 4f648bbfa8a36..fcbccc3cea4ab 100644 --- a/op-chain-ops/deployer/opcm/superchain.go +++ b/op-deployer/pkg/deployer/opcm/superchain.go @@ -1,7 +1,6 @@ package opcm import ( - "fmt" "math/big" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" @@ -53,37 +52,5 @@ type DeploySuperchainOpts struct { } func DeploySuperchain(h *script.Host, input DeploySuperchainInput) (DeploySuperchainOutput, error) { - var dso DeploySuperchainOutput - - inputAddr := h.NewScriptAddress() - outputAddr := h.NewScriptAddress() - - cleanupInput, err := script.WithPrecompileAtAddress[*DeploySuperchainInput](h, inputAddr, &input) - if err != nil { - return dso, fmt.Errorf("failed to insert DeploySuperchainInput precompile: %w", err) - } - defer cleanupInput() - - cleanupOutput, err := script.WithPrecompileAtAddress[*DeploySuperchainOutput]( - h, - outputAddr, - &dso, - script.WithFieldSetter[*DeploySuperchainOutput], - ) - if err != nil { - return dso, fmt.Errorf("failed to insert DeploySuperchainOutput precompile: %w", err) - } - defer cleanupOutput() - - deployScript, cleanupDeploy, err := script.WithScript[DeploySuperchainScript](h, "DeploySuperchain.s.sol", "DeploySuperchain") - if err != nil { - return dso, fmt.Errorf("failed to load DeploySuperchain script: %w", err) - } - defer cleanupDeploy() - - if err := deployScript.Run(inputAddr, outputAddr); err != nil { - return dso, fmt.Errorf("failed to run DeploySuperchain script: %w", err) - } - - return dso, nil + return RunBasicScript[DeploySuperchainInput, DeploySuperchainOutput](h, input, "DeploySuperchain.s.sol", "DeploySuperchain") } diff --git a/op-deployer/pkg/deployer/pipeline/alt_da.go b/op-deployer/pkg/deployer/pipeline/alt_da.go new file mode 100644 index 0000000000000..b364126209451 --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/alt_da.go @@ -0,0 +1,56 @@ +package pipeline + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum/go-ethereum/common" +) + +func DeployAltDA(env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error { + lgr := env.Logger.New("stage", "deploy-alt-da") + + chainIntent, err := intent.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain intent: %w", err) + } + + chainState, err := st.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain state: %w", err) + } + + if !shouldDeployAltDA(chainIntent, chainState) { + lgr.Info("alt-da deployment not needed") + return nil + } + + var dao opcm.DeployAltDAOutput + lgr.Info("deploying alt-da contracts") + dao, err = opcm.DeployAltDA(env.L1ScriptHost, opcm.DeployAltDAInput{ + Salt: st.Create2Salt, + ProxyAdmin: chainState.ProxyAdminAddress, + ChallengeContractOwner: chainIntent.Roles.L1ProxyAdminOwner, + ChallengeWindow: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DAChallengeWindow), + ResolveWindow: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DAResolveWindow), + BondSize: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DABondSize), + ResolverRefundPercentage: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DAResolverRefundPercentage), + }) + if err != nil { + return fmt.Errorf("failed to deploy alt-da contracts: %w", err) + } + + chainState.DataAvailabilityChallengeProxyAddress = dao.DataAvailabilityChallengeProxy + chainState.DataAvailabilityChallengeImplAddress = dao.DataAvailabilityChallengeImpl + return nil +} + +func shouldDeployAltDA(chainIntent *state.ChainIntent, chainState *state.ChainState) bool { + if !chainIntent.DangerousAltDAConfig.UseAltDA { + return false + } + + return chainState.DataAvailabilityChallengeImplAddress == common.Address{} +} diff --git a/op-deployer/pkg/deployer/pipeline/env.go b/op-deployer/pkg/deployer/pipeline/env.go new file mode 100644 index 0000000000000..20d8daaf76fa1 --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/env.go @@ -0,0 +1,80 @@ +package pipeline + +import ( + "context" + "fmt" + "path" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +type Env struct { + StateWriter StateWriter + L1ScriptHost *script.Host + L1Client *ethclient.Client + Broadcaster broadcaster.Broadcaster + Deployer common.Address + Logger log.Logger +} + +type StateWriter interface { + WriteState(st *state.State) error +} + +type stateWriterFunc func(st *state.State) error + +func (f stateWriterFunc) WriteState(st *state.State) error { + return f(st) +} + +func WorkdirStateWriter(workdir string) StateWriter { + return stateWriterFunc(func(st *state.State) error { + return WriteState(workdir, st) + }) +} + +func NoopStateWriter() StateWriter { + return stateWriterFunc(func(st *state.State) error { + return nil + }) +} + +func ReadIntent(workdir string) (*state.Intent, error) { + intentPath := path.Join(workdir, "intent.toml") + intent, err := jsonutil.LoadTOML[state.Intent](intentPath) + if err != nil { + return nil, fmt.Errorf("failed to read intent file: %w", err) + } + return intent, nil +} + +func ReadState(workdir string) (*state.State, error) { + statePath := path.Join(workdir, "state.json") + st, err := jsonutil.LoadJSON[state.State](statePath) + if err != nil { + return nil, fmt.Errorf("failed to read state file: %w", err) + } + return st, nil +} + +func WriteState(workdir string, st *state.State) error { + statePath := path.Join(workdir, "state.json") + return st.WriteToFile(statePath) +} + +type ArtifactsBundle struct { + L1 foundry.StatDirFs + L2 foundry.StatDirFs +} + +type Stage func(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State) error diff --git a/op-deployer/pkg/deployer/pipeline/implementations.go b/op-deployer/pkg/deployer/pipeline/implementations.go new file mode 100644 index 0000000000000..c2d409b5c3a49 --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/implementations.go @@ -0,0 +1,103 @@ +package pipeline + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" +) + +type SuperchainProofParams struct { + WithdrawalDelaySeconds uint64 `json:"withdrawalDelaySeconds" toml:"withdrawalDelaySeconds"` + MinProposalSizeBytes uint64 `json:"minProposalSizeBytes" toml:"minProposalSizeBytes"` + ChallengePeriodSeconds uint64 `json:"challengePeriodSeconds" toml:"challengePeriodSeconds"` + ProofMaturityDelaySeconds uint64 `json:"proofMaturityDelaySeconds" toml:"proofMaturityDelaySeconds"` + DisputeGameFinalityDelaySeconds uint64 `json:"disputeGameFinalityDelaySeconds" toml:"disputeGameFinalityDelaySeconds"` + MIPSVersion uint64 `json:"mipsVersion" toml:"mipsVersion"` +} + +func DeployImplementations(env *Env, intent *state.Intent, st *state.State) error { + lgr := env.Logger.New("stage", "deploy-implementations") + + if !shouldDeployImplementations(intent, st) { + lgr.Info("implementations deployment not needed") + return nil + } + + lgr.Info("deploying implementations") + + var standardVersionsTOML string + var contractsRelease string + var err error + if intent.L1ContractsLocator.IsTag() && intent.DeploymentStrategy == state.DeploymentStrategyLive { + standardVersionsTOML, err = standard.L1VersionsDataFor(intent.L1ChainID) + if err == nil { + contractsRelease = intent.L1ContractsLocator.Tag + } else { + contractsRelease = "dev" + } + + } else { + contractsRelease = "dev" + } + + proofParams, err := jsonutil.MergeJSON( + SuperchainProofParams{ + WithdrawalDelaySeconds: standard.WithdrawalDelaySeconds, + MinProposalSizeBytes: standard.MinProposalSizeBytes, + ChallengePeriodSeconds: standard.ChallengePeriodSeconds, + ProofMaturityDelaySeconds: standard.ProofMaturityDelaySeconds, + DisputeGameFinalityDelaySeconds: standard.DisputeGameFinalityDelaySeconds, + MIPSVersion: standard.MIPSVersion, + }, + intent.GlobalDeployOverrides, + ) + if err != nil { + return fmt.Errorf("error merging proof params from overrides: %w", err) + } + + dio, err := opcm.DeployImplementations( + env.L1ScriptHost, + opcm.DeployImplementationsInput{ + Salt: st.Create2Salt, + WithdrawalDelaySeconds: new(big.Int).SetUint64(proofParams.WithdrawalDelaySeconds), + MinProposalSizeBytes: new(big.Int).SetUint64(proofParams.MinProposalSizeBytes), + ChallengePeriodSeconds: new(big.Int).SetUint64(proofParams.ChallengePeriodSeconds), + ProofMaturityDelaySeconds: new(big.Int).SetUint64(proofParams.ProofMaturityDelaySeconds), + DisputeGameFinalityDelaySeconds: new(big.Int).SetUint64(proofParams.DisputeGameFinalityDelaySeconds), + MipsVersion: new(big.Int).SetUint64(proofParams.MIPSVersion), + L1ContractsRelease: contractsRelease, + SuperchainConfigProxy: st.SuperchainDeployment.SuperchainConfigProxyAddress, + ProtocolVersionsProxy: st.SuperchainDeployment.ProtocolVersionsProxyAddress, + StandardVersionsToml: standardVersionsTOML, + UseInterop: intent.UseInterop, + }, + ) + if err != nil { + return fmt.Errorf("error deploying implementations: %w", err) + } + + st.ImplementationsDeployment = &state.ImplementationsDeployment{ + OpcmAddress: dio.Opcm, + DelayedWETHImplAddress: dio.DelayedWETHImpl, + OptimismPortalImplAddress: dio.OptimismPortalImpl, + PreimageOracleSingletonAddress: dio.PreimageOracleSingleton, + MipsSingletonAddress: dio.MipsSingleton, + SystemConfigImplAddress: dio.SystemConfigImpl, + L1CrossDomainMessengerImplAddress: dio.L1CrossDomainMessengerImpl, + L1ERC721BridgeImplAddress: dio.L1ERC721BridgeImpl, + L1StandardBridgeImplAddress: dio.L1StandardBridgeImpl, + OptimismMintableERC20FactoryImplAddress: dio.OptimismMintableERC20FactoryImpl, + DisputeGameFactoryImplAddress: dio.DisputeGameFactoryImpl, + } + + return nil +} + +func shouldDeployImplementations(intent *state.Intent, st *state.State) bool { + return st.ImplementationsDeployment == nil +} diff --git a/op-chain-ops/deployer/pipeline/init.go b/op-deployer/pkg/deployer/pipeline/init.go similarity index 54% rename from op-chain-ops/deployer/pipeline/init.go rename to op-deployer/pkg/deployer/pipeline/init.go index d7009e117269b..2b9f1cbd45fcb 100644 --- a/op-chain-ops/deployer/pipeline/init.go +++ b/op-deployer/pkg/deployer/pipeline/init.go @@ -3,46 +3,44 @@ package pipeline import ( "context" "crypto/rand" + "errors" "fmt" + "os" "strings" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" "github.com/ethereum-optimism/optimism/op-chain-ops/script" "github.com/ethereum/go-ethereum/common" - - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" ) +var ErrRefusingToDeployTaggedReleaseWithoutOPCM = errors.New("refusing to deploy tagged release without OPCM") + func IsSupportedStateVersion(version int) bool { return version == 1 } -func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Intent, st *state.State) error { - lgr := env.Logger.New("stage", "init") +func InitLiveStrategy(ctx context.Context, env *Env, intent *state.Intent, st *state.State) error { + lgr := env.Logger.New("stage", "init", "strategy", "live") lgr.Info("initializing pipeline") - // Ensure the state version is supported. - if !IsSupportedStateVersion(st.Version) { - return fmt.Errorf("unsupported state version: %d", st.Version) + if err := initCommonChecks(st); err != nil { + return err } - if st.Create2Salt == (common.Hash{}) { - _, err := rand.Read(st.Create2Salt[:]) - if err != nil { - return fmt.Errorf("failed to generate CREATE2 salt: %w", err) - } - } + opcmAddress, opcmAddrErr := standard.ManagerImplementationAddrFor(intent.L1ChainID) + hasPredeployedOPCM := opcmAddrErr == nil + isTag := intent.L1ContractsLocator.IsTag() - if strings.HasPrefix(intent.ContractsRelease, "op-contracts") { - superCfg, err := opcm.SuperchainFor(intent.L1ChainID) + if isTag && hasPredeployedOPCM { + superCfg, err := standard.SuperchainFor(intent.L1ChainID) if err != nil { return fmt.Errorf("error getting superchain config: %w", err) } - proxyAdmin, err := opcm.ManagerOwnerAddrFor(intent.L1ChainID) + proxyAdmin, err := standard.ManagerOwnerAddrFor(intent.L1ChainID) if err != nil { return fmt.Errorf("error getting superchain proxy admin address: %w", err) } @@ -55,15 +53,32 @@ func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Inte SuperchainConfigProxyAddress: common.Address(*superCfg.Config.SuperchainConfigAddr), } - opcmProxy, err := opcm.ManagerImplementationAddrFor(intent.L1ChainID) - if err != nil { - return fmt.Errorf("error getting OPCM proxy address: %w", err) - } st.ImplementationsDeployment = &state.ImplementationsDeployment{ - OpcmProxyAddress: opcmProxy, + OpcmAddress: opcmAddress, + } + } else if isTag && !hasPredeployedOPCM { + if err := displayWarning(); err != nil { + return err } } + l1ChainID, err := env.L1Client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get L1 chain ID: %w", err) + } + + if l1ChainID.Cmp(intent.L1ChainIDBig()) != 0 { + return fmt.Errorf("l1 chain ID mismatch: got %d, expected %d", l1ChainID, intent.L1ChainID) + } + + deployerCode, err := env.L1Client.CodeAt(ctx, script.DeterministicDeployerAddress, nil) + if err != nil { + return fmt.Errorf("failed to get deployer code: %w", err) + } + if len(deployerCode) == 0 { + return fmt.Errorf("deterministic deployer is not deployed on this chain - please deploy it first") + } + // If the state has never been applied, we don't need to perform // any additional checks. if st.AppliedIntent == nil { @@ -80,24 +95,39 @@ func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Inte return immutableErr("fundDevAccounts", st.AppliedIntent.FundDevAccounts, intent.FundDevAccounts) } - l1ChainID, err := env.L1Client.ChainID(ctx) - if err != nil { - return fmt.Errorf("failed to get L1 chain ID: %w", err) + // TODO: validate individual + + return nil +} + +func initCommonChecks(st *state.State) error { + // Ensure the state version is supported. + if !IsSupportedStateVersion(st.Version) { + return fmt.Errorf("unsupported state version: %d", st.Version) } - if l1ChainID.Cmp(intent.L1ChainIDBig()) != 0 { - return fmt.Errorf("L1 chain ID mismatch: got %d, expected %d", l1ChainID, intent.L1ChainID) + if st.Create2Salt == (common.Hash{}) { + _, err := rand.Read(st.Create2Salt[:]) + if err != nil { + return fmt.Errorf("failed to generate CREATE2 salt: %w", err) + } } + return nil +} - deployerCode, err := env.L1Client.CodeAt(ctx, script.DeterministicDeployerAddress, nil) - if err != nil { - return fmt.Errorf("failed to get deployer code: %w", err) +func InitGenesisStrategy(env *Env, intent *state.Intent, st *state.State) error { + lgr := env.Logger.New("stage", "init", "strategy", "genesis") + lgr.Info("initializing pipeline") + + if err := initCommonChecks(st); err != nil { + return err } - if len(deployerCode) == 0 { - return fmt.Errorf("deterministic deployer is not deployed on this chain - please deploy it first") + + if intent.SuperchainRoles == nil { + return fmt.Errorf("superchain roles must be set for genesis strategy") } - // TODO: validate individual + // Mostly a stub for now. return nil } @@ -105,3 +135,23 @@ func Init(ctx context.Context, env *Env, _ foundry.StatDirFs, intent *state.Inte func immutableErr(field string, was, is any) error { return fmt.Errorf("%s is immutable: was %v, is %v", field, was, is) } + +func displayWarning() error { + warning := strings.TrimPrefix(` +####################### WARNING! WARNING WARNING! ####################### + +You are deploying a tagged release to a chain with no pre-deployed OPCM. +Due to a quirk of our contract version system, this can lead to deploying +contracts containing unaudited or untested code. As a result, this +functionality is currently disabled. + +We will fix this in an upcoming release. + +This process will now exit. + +####################### WARNING! WARNING WARNING! ####################### +`, "\n") + + _, _ = fmt.Fprint(os.Stderr, warning) + return ErrRefusingToDeployTaggedReleaseWithoutOPCM +} diff --git a/op-deployer/pkg/deployer/pipeline/l2genesis.go b/op-deployer/pkg/deployer/pipeline/l2genesis.go new file mode 100644 index 0000000000000..945cb5af8ff09 --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/l2genesis.go @@ -0,0 +1,78 @@ +package pipeline + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + + "github.com/ethereum/go-ethereum/common" +) + +func GenerateL2Genesis(pEnv *Env, intent *state.Intent, bundle ArtifactsBundle, st *state.State, chainID common.Hash) error { + lgr := pEnv.Logger.New("stage", "generate-l2-genesis") + + thisIntent, err := intent.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain intent: %w", err) + } + + thisChainState, err := st.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain state: %w", err) + } + + if !shouldGenerateL2Genesis(thisChainState) { + lgr.Info("L2 genesis generation not needed") + return nil + } + + lgr.Info("generating L2 genesis", "id", chainID.Hex()) + + initCfg, err := state.CombineDeployConfig(intent, thisIntent, st, thisChainState) + if err != nil { + return fmt.Errorf("failed to combine L2 init config: %w", err) + } + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + pEnv.Logger, + pEnv.Deployer, + bundle.L2, + ) + if err != nil { + return fmt.Errorf("failed to create L2 script host: %w", err) + } + + if err := opcm.L2Genesis(host, &opcm.L2GenesisInput{ + L1Deployments: opcm.L1Deployments{ + L1CrossDomainMessengerProxy: thisChainState.L1CrossDomainMessengerProxyAddress, + L1StandardBridgeProxy: thisChainState.L1StandardBridgeProxyAddress, + L1ERC721BridgeProxy: thisChainState.L1ERC721BridgeProxyAddress, + }, + L2Config: initCfg.L2InitializationConfig, + }); err != nil { + return fmt.Errorf("failed to call L2Genesis script: %w", err) + } + + host.Wipe(pEnv.Deployer) + + dump, err := host.StateDump() + if err != nil { + return fmt.Errorf("failed to dump state: %w", err) + } + + thisChainState.Allocs = &state.GzipData[foundry.ForgeAllocs]{ + Data: dump, + } + + return nil +} + +func shouldGenerateL2Genesis(thisChainState *state.ChainState) bool { + return thisChainState.Allocs == nil +} diff --git a/op-deployer/pkg/deployer/pipeline/opchain.go b/op-deployer/pkg/deployer/pipeline/opchain.go new file mode 100644 index 0000000000000..90cc665e077dd --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/opchain.go @@ -0,0 +1,183 @@ +package pipeline + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum/go-ethereum/common" +) + +func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error { + lgr := env.Logger.New("stage", "deploy-opchain") + + if !shouldDeployOPChain(st, chainID) { + lgr.Info("opchain deployment not needed") + return nil + } + + thisIntent, err := intent.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain intent: %w", err) + } + + var opcmAddr common.Address + var deployFunc func() (opcm.DeployOPChainOutput, error) + switch intent.L1ContractsLocator.Tag { + case standard.ContractsV160Tag, standard.ContractsV170Beta1L2Tag: + deployFunc = func() (opcm.DeployOPChainOutput, error) { + input, err := makeDCIV160(intent, thisIntent, chainID, st) + if err != nil { + return opcm.DeployOPChainOutput{}, fmt.Errorf("error making deploy OP chain input: %w", err) + } + + opcmAddr = input.Opcm + return opcm.DeployOPChainV160(env.L1ScriptHost, input) + } + default: + deployFunc = func() (opcm.DeployOPChainOutput, error) { + input, err := makeDCIIsthmus(intent, thisIntent, chainID, st) + if err != nil { + return opcm.DeployOPChainOutput{}, fmt.Errorf("error making deploy OP chain input: %w", err) + } + + opcmAddr = input.Opcm + return opcm.DeployOPChainIsthmus(env.L1ScriptHost, input) + } + } + + var dco opcm.DeployOPChainOutput + lgr.Info("deploying OP chain using local allocs", "id", chainID.Hex()) + dco, err = deployFunc() + if err != nil { + return fmt.Errorf("error deploying OP chain: %w", err) + } + + st.Chains = append(st.Chains, makeChainState(chainID, dco)) + + var release string + if intent.L1ContractsLocator.IsTag() { + release = intent.L1ContractsLocator.Tag + } else { + release = "dev" + } + + readInput := opcm.ReadImplementationAddressesInput{ + DeployOPChainOutput: dco, + Opcm: opcmAddr, + Release: release, + } + impls, err := opcm.ReadImplementationAddresses(env.L1ScriptHost, readInput) + if err != nil { + return fmt.Errorf("failed to read implementation addresses: %w", err) + } + + st.ImplementationsDeployment.DelayedWETHImplAddress = impls.DelayedWETH + st.ImplementationsDeployment.OptimismPortalImplAddress = impls.OptimismPortal + st.ImplementationsDeployment.SystemConfigImplAddress = impls.SystemConfig + st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress = impls.L1CrossDomainMessenger + st.ImplementationsDeployment.L1ERC721BridgeImplAddress = impls.L1ERC721Bridge + st.ImplementationsDeployment.L1StandardBridgeImplAddress = impls.L1StandardBridge + st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress = impls.OptimismMintableERC20Factory + st.ImplementationsDeployment.DisputeGameFactoryImplAddress = impls.DisputeGameFactory + st.ImplementationsDeployment.MipsSingletonAddress = impls.MipsSingleton + st.ImplementationsDeployment.PreimageOracleSingletonAddress = impls.PreimageOracleSingleton + + return nil +} + +type ChainProofParams struct { + DisputeGameType uint32 `json:"disputeGameType" toml:"disputeGameType"` + DisputeAbsolutePrestate common.Hash `json:"disputeAbsolutePrestate" toml:"disputeAbsolutePrestate"` + DisputeMaxGameDepth uint64 `json:"disputeMaxGameDepth" toml:"disputeMaxGameDepth"` + DisputeSplitDepth uint64 `json:"disputeSplitDepth" toml:"disputeSplitDepth"` + DisputeClockExtension uint64 `json:"disputeClockExtension" toml:"disputeClockExtension"` + DisputeMaxClockDuration uint64 `json:"disputeMaxClockDuration" toml:"disputeMaxClockDuration"` + DangerouslyAllowCustomDisputeParameters bool `json:"dangerouslyAllowCustomDisputeParameters" toml:"dangerouslyAllowCustomDisputeParameters"` +} + +func makeDCIV160(intent *state.Intent, thisIntent *state.ChainIntent, chainID common.Hash, st *state.State) (opcm.DeployOPChainInputV160, error) { + proofParams, err := jsonutil.MergeJSON( + ChainProofParams{ + DisputeGameType: standard.DisputeGameType, + DisputeAbsolutePrestate: standard.DisputeAbsolutePrestate, + DisputeMaxGameDepth: standard.DisputeMaxGameDepth, + DisputeSplitDepth: standard.DisputeSplitDepth, + DisputeClockExtension: standard.DisputeClockExtension, + DisputeMaxClockDuration: standard.DisputeMaxClockDuration, + }, + intent.GlobalDeployOverrides, + thisIntent.DeployOverrides, + ) + if err != nil { + return opcm.DeployOPChainInputV160{}, fmt.Errorf("error merging proof params from overrides: %w", err) + } + + return opcm.DeployOPChainInputV160{ + OpChainProxyAdminOwner: thisIntent.Roles.L1ProxyAdminOwner, + SystemConfigOwner: thisIntent.Roles.SystemConfigOwner, + Batcher: thisIntent.Roles.Batcher, + UnsafeBlockSigner: thisIntent.Roles.UnsafeBlockSigner, + Proposer: thisIntent.Roles.Proposer, + Challenger: thisIntent.Roles.Challenger, + BasefeeScalar: standard.BasefeeScalar, + BlobBaseFeeScalar: standard.BlobBaseFeeScalar, + L2ChainId: chainID.Big(), + Opcm: st.ImplementationsDeployment.OpcmAddress, + SaltMixer: st.Create2Salt.String(), // passing through salt generated at state initialization + GasLimit: standard.GasLimit, + DisputeGameType: proofParams.DisputeGameType, + DisputeAbsolutePrestate: proofParams.DisputeAbsolutePrestate, + DisputeMaxGameDepth: proofParams.DisputeMaxGameDepth, + DisputeSplitDepth: proofParams.DisputeSplitDepth, + DisputeClockExtension: proofParams.DisputeClockExtension, // 3 hours (input in seconds) + DisputeMaxClockDuration: proofParams.DisputeMaxClockDuration, // 3.5 days (input in seconds) + AllowCustomDisputeParameters: proofParams.DangerouslyAllowCustomDisputeParameters, + }, nil +} + +func makeDCIIsthmus(intent *state.Intent, thisIntent *state.ChainIntent, chainID common.Hash, st *state.State) (opcm.DeployOPChainInputIsthmus, error) { + dci, err := makeDCIV160(intent, thisIntent, chainID, st) + if err != nil { + return opcm.DeployOPChainInputIsthmus{}, fmt.Errorf("error making deploy OP chain input: %w", err) + } + + return opcm.DeployOPChainInputIsthmus{ + DeployOPChainInputV160: dci, + SystemConfigFeeAdmin: common.Address{'D', 'E', 'A', 'D'}, + }, nil +} + +func makeChainState(chainID common.Hash, dco opcm.DeployOPChainOutput) *state.ChainState { + return &state.ChainState{ + ID: chainID, + ProxyAdminAddress: dco.OpChainProxyAdmin, + AddressManagerAddress: dco.AddressManager, + L1ERC721BridgeProxyAddress: dco.L1ERC721BridgeProxy, + SystemConfigProxyAddress: dco.SystemConfigProxy, + OptimismMintableERC20FactoryProxyAddress: dco.OptimismMintableERC20FactoryProxy, + L1StandardBridgeProxyAddress: dco.L1StandardBridgeProxy, + L1CrossDomainMessengerProxyAddress: dco.L1CrossDomainMessengerProxy, + OptimismPortalProxyAddress: dco.OptimismPortalProxy, + DisputeGameFactoryProxyAddress: dco.DisputeGameFactoryProxy, + AnchorStateRegistryProxyAddress: dco.AnchorStateRegistryProxy, + AnchorStateRegistryImplAddress: dco.AnchorStateRegistryImpl, + FaultDisputeGameAddress: dco.FaultDisputeGame, + PermissionedDisputeGameAddress: dco.PermissionedDisputeGame, + DelayedWETHPermissionedGameProxyAddress: dco.DelayedWETHPermissionedGameProxy, + DelayedWETHPermissionlessGameProxyAddress: dco.DelayedWETHPermissionlessGameProxy, + } +} + +func shouldDeployOPChain(st *state.State, chainID common.Hash) bool { + for _, chain := range st.Chains { + if chain.ID == chainID { + return false + } + } + + return true +} diff --git a/op-deployer/pkg/deployer/pipeline/start_block.go b/op-deployer/pkg/deployer/pipeline/start_block.go new file mode 100644 index 0000000000000..19e8ee5aea184 --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/start_block.go @@ -0,0 +1,63 @@ +package pipeline + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func SetStartBlockLiveStrategy(ctx context.Context, env *Env, st *state.State, chainID common.Hash) error { + lgr := env.Logger.New("stage", "set-start-block", "strategy", "live") + lgr.Info("setting start block", "id", chainID.Hex()) + + thisChainState, err := st.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain state: %w", err) + } + + startHeader, err := env.L1Client.HeaderByNumber(ctx, nil) + if err != nil { + return fmt.Errorf("failed to get start block: %w", err) + } + thisChainState.StartBlock = startHeader + + return nil +} + +func SetStartBlockGenesisStrategy(env *Env, st *state.State, chainID common.Hash) error { + lgr := env.Logger.New("stage", "set-start-block", "strategy", "genesis") + lgr.Info("setting start block", "id", chainID.Hex()) + + thisChainState, err := st.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain state: %w", err) + } + + deployConfig := &genesis.DeployConfig{ + DevL1DeployConfig: genesis.DevL1DeployConfig{ + L1BlockTime: 12, + L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()), + }, + L2InitializationConfig: genesis.L2InitializationConfig{ + L2CoreDeployConfig: genesis.L2CoreDeployConfig{ + L1ChainID: 900, + }, + DevDeployConfig: genesis.DevDeployConfig{ + FundDevAccounts: true, + }, + }, + } + + devGenesis, err := genesis.BuildL1DeveloperGenesis(deployConfig, st.L1StateDump.Data, &genesis.L1Deployments{}) + if err != nil { + return fmt.Errorf("failed to build L1 developer genesis: %w", err) + } + thisChainState.StartBlock = devGenesis.ToBlock().Header() + + return nil +} diff --git a/op-deployer/pkg/deployer/pipeline/superchain.go b/op-deployer/pkg/deployer/pipeline/superchain.go new file mode 100644 index 0000000000000..564028e36c00e --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/superchain.go @@ -0,0 +1,50 @@ +package pipeline + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + + "github.com/ethereum-optimism/optimism/op-node/rollup" +) + +func DeploySuperchain(env *Env, intent *state.Intent, st *state.State) error { + lgr := env.Logger.New("stage", "deploy-superchain") + + if !shouldDeploySuperchain(intent, st) { + lgr.Info("superchain deployment not needed") + return nil + } + + lgr.Info("deploying superchain") + + dso, err := opcm.DeploySuperchain( + env.L1ScriptHost, + opcm.DeploySuperchainInput{ + SuperchainProxyAdminOwner: intent.SuperchainRoles.ProxyAdminOwner, + ProtocolVersionsOwner: intent.SuperchainRoles.ProtocolVersionsOwner, + Guardian: intent.SuperchainRoles.Guardian, + Paused: false, + RequiredProtocolVersion: rollup.OPStackSupport, + RecommendedProtocolVersion: rollup.OPStackSupport, + }, + ) + if err != nil { + return fmt.Errorf("failed to deploy superchain: %w", err) + } + + st.SuperchainDeployment = &state.SuperchainDeployment{ + ProxyAdminAddress: dso.SuperchainProxyAdmin, + SuperchainConfigProxyAddress: dso.SuperchainConfigProxy, + SuperchainConfigImplAddress: dso.SuperchainConfigImpl, + ProtocolVersionsProxyAddress: dso.ProtocolVersionsProxy, + ProtocolVersionsImplAddress: dso.ProtocolVersionsImpl, + } + + return nil +} + +func shouldDeploySuperchain(intent *state.Intent, st *state.State) bool { + return st.SuperchainDeployment == nil +} diff --git a/op-chain-ops/deployer/opcm/standard-versions-mainnet.toml b/op-deployer/pkg/deployer/standard/standard-versions-mainnet.toml similarity index 100% rename from op-chain-ops/deployer/opcm/standard-versions-mainnet.toml rename to op-deployer/pkg/deployer/standard/standard-versions-mainnet.toml diff --git a/op-deployer/pkg/deployer/standard/standard-versions-sepolia.toml b/op-deployer/pkg/deployer/standard/standard-versions-sepolia.toml new file mode 100644 index 0000000000000..25b63b4ac65d1 --- /dev/null +++ b/op-deployer/pkg/deployer/standard/standard-versions-sepolia.toml @@ -0,0 +1,42 @@ +[releases] + +# Contracts which are +# * unproxied singletons: specify a standard "address" +# * proxied : specify a standard "implementation_address" +# * neither : specify neither a standard "address" nor "implementation_address" + +# Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.3 +[releases."op-contracts/v1.8.0-rc.3"] +# Updated in this release +system_config = { version = "2.3.0", implementation_address = "0x33b83E4C305c908B2Fc181dDa36e230213058d7d" } # UPDATED IN THIS RELEASE +fault_dispute_game = { version = "1.3.1" } # UPDATED IN THIS RELEASE +permissioned_dispute_game = { version = "1.3.1" } # UPDATED IN THIS RELEASE +mips = { version = "1.2.1", address = "0x69470D6970Cd2A006b84B1d4d70179c892cFCE01" } # UPDATED IN THIS RELEASE +# Unchanged in this release +optimism_portal = { version = "3.10.0", implementation_address = "0x35028bae87d71cbc192d545d38f960ba30b4b233" } +anchor_state_registry = { version = "2.0.0" } +delayed_weth = { version = "1.1.0", implementation_address = "0x07f69b19532476c6cd03056d6bc3f1b110ab7538" } +dispute_game_factory = { version = "1.0.0", implementation_address = "0xa51bea7e4d34206c0bcb04a776292f2f19f0beec" } +preimage_oracle = { version = "1.1.2", address = "0x92240135b46fc1142dA181f550aE8f595B858854" } +l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } +l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d" } +l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64b5a5ed26dcb17370ff4d33a8d503f0fbd06cff" } +# l2_output_oracle -- This contract not used in fault proofs +optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xe01efbeb1089d1d1db9c6c8b135c934c0734c846" } + +# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 +[releases."op-contracts/v1.6.0"] +optimism_portal = { version = "3.10.0", implementation_address = "0x35028bae87d71cbc192d545d38f960ba30b4b233" } +system_config = { version = "2.2.0", implementation_address = "0xCcdd86d581e40fb5a1C77582247BC493b6c8B169" } +anchor_state_registry = { version = "2.0.0" } +delayed_weth = { version = "1.1.0", implementation_address = "0x07f69b19532476c6cd03056d6bc3f1b110ab7538" } +dispute_game_factory = { version = "1.0.0", implementation_address = "0xa51bea7e4d34206c0bcb04a776292f2f19f0beec" } +fault_dispute_game = { version = "1.3.0" } +permissioned_dispute_game = { version = "1.3.0" } +mips = { version = "1.1.0", address = "0x47B0E34C1054009e696BaBAAd56165e1e994144d" } +preimage_oracle = { version = "1.1.2", address = "0x92240135b46fc1142dA181f550aE8f595B858854" } +l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } +l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d" } +l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64b5a5ed26dcb17370ff4d33a8d503f0fbd06cff" } +# l2_output_oracle -- This contract not used in fault proofs +optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xe01efbeb1089d1d1db9c6c8b135c934c0734c846" } \ No newline at end of file diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go new file mode 100644 index 0000000000000..72cff3cb92696 --- /dev/null +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -0,0 +1,296 @@ +package standard + +import ( + "embed" + "fmt" + "net/url" + + "github.com/BurntSushi/toml" + + "github.com/ethereum-optimism/superchain-registry/superchain" + "github.com/ethereum/go-ethereum/common" +) + +const ( + GasLimit uint64 = 60_000_000 + BasefeeScalar uint32 = 1368 + BlobBaseFeeScalar uint32 = 801949 + WithdrawalDelaySeconds uint64 = 604800 + MinProposalSizeBytes uint64 = 126000 + ChallengePeriodSeconds uint64 = 86400 + ProofMaturityDelaySeconds uint64 = 604800 + DisputeGameFinalityDelaySeconds uint64 = 302400 + MIPSVersion uint64 = 1 + DisputeGameType uint32 = 1 // PERMISSIONED game type + DisputeMaxGameDepth uint64 = 73 + DisputeSplitDepth uint64 = 30 + DisputeClockExtension uint64 = 10800 + DisputeMaxClockDuration uint64 = 302400 + Eip1559DenominatorCanyon uint64 = 250 + Eip1559Denominator uint64 = 50 + Eip1559Elasticity uint64 = 6 + + ContractsV160Tag = "op-contracts/v1.6.0" + ContractsV170Beta1L2Tag = "op-contracts/v1.7.0-beta.1+l2-contracts" +) + +var DisputeAbsolutePrestate = common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") + +//go:embed standard-versions-mainnet.toml +var VersionsMainnetData string + +//go:embed standard-versions-sepolia.toml +var VersionsSepoliaData string + +var L1VersionsSepolia L1Versions + +var L1VersionsMainnet L1Versions + +var DefaultL1ContractsTag = ContractsV160Tag + +var DefaultL2ContractsTag = ContractsV170Beta1L2Tag + +type L1Versions struct { + Releases map[string]L1VersionsReleases `toml:"releases"` +} + +type L1VersionsReleases struct { + OptimismPortal VersionRelease `toml:"optimism_portal"` + SystemConfig VersionRelease `toml:"system_config"` + AnchorStateRegistry VersionRelease `toml:"anchor_state_registry"` + DelayedWETH VersionRelease `toml:"delayed_weth"` + DisputeGameFactory VersionRelease `toml:"dispute_game_factory"` + FaultDisputeGame VersionRelease `toml:"fault_dispute_game"` + PermissionedDisputeGame VersionRelease `toml:"permissioned_dispute_game"` + MIPS VersionRelease `toml:"mips"` + PreimageOracle VersionRelease `toml:"preimage_oracle"` + L1CrossDomainMessenger VersionRelease `toml:"l1_cross_domain_messenger"` + L1ERC721Bridge VersionRelease `toml:"l1_erc721_bridge"` + L1StandardBridge VersionRelease `toml:"l1_standard_bridge"` + OptimismMintableERC20Factory VersionRelease `toml:"optimism_mintable_erc20_factory"` +} + +type VersionRelease struct { + Version string `toml:"version"` + ImplementationAddress common.Address `toml:"implementation_address"` + Address common.Address `toml:"address"` +} + +var _ embed.FS + +type OPCMBlueprints struct { + AddressManager common.Address + Proxy common.Address + ProxyAdmin common.Address + L1ChugSplashProxy common.Address + ResolvedDelegateProxy common.Address + AnchorStateRegistry common.Address + PermissionedDisputeGame1 common.Address + PermissionedDisputeGame2 common.Address +} + +var sepoliaBlueprints = OPCMBlueprints{ + AddressManager: common.HexToAddress("0x3125a4cB2179E04203D3Eb2b5784aaef9FD64216"), + Proxy: common.HexToAddress("0xe650ADb86a0de96e2c434D0a52E7D5B70980D6f1"), + ProxyAdmin: common.HexToAddress("0x3AC6b88F6bC4A5038DB7718dE47a5ab1a9609319"), + L1ChugSplashProxy: common.HexToAddress("0x58770FC7ed304c43D2B70248914eb34A741cF411"), + ResolvedDelegateProxy: common.HexToAddress("0x0449adB72D489a137d476aB49c6b812161754fD3"), + AnchorStateRegistry: common.HexToAddress("0xB98095199437883b7661E0D58256060f3bc730a4"), + PermissionedDisputeGame1: common.HexToAddress("0xf72Ac5f164cC024DE09a2c249441715b69a16eAb"), + PermissionedDisputeGame2: common.HexToAddress("0x713dAC5A23728477547b484f9e0D751077E300a2"), +} + +var mainnetBlueprints = OPCMBlueprints{ + AddressManager: common.HexToAddress("0x29aA24714c06914d9689e933cae2293C569AfeEa"), + Proxy: common.HexToAddress("0x3626ebD458c7f34FD98789A373593fF2fc227bA0"), + ProxyAdmin: common.HexToAddress("0x7170678A5CFFb6872606d251B3CcdB27De962631"), + L1ChugSplashProxy: common.HexToAddress("0x538906C8B000D621fd11B7e8642f504dD8730837"), + ResolvedDelegateProxy: common.HexToAddress("0xF12bD34d6a1d26d230240ECEA761f77e2013926E"), + AnchorStateRegistry: common.HexToAddress("0xbA7Be2bEE016568274a4D1E6c852Bb9a99FaAB8B"), + PermissionedDisputeGame1: common.HexToAddress("0xb94bF6130Df8BD9a9eA45D8dD8C18957002d1986"), + PermissionedDisputeGame2: common.HexToAddress("0xe0a642B249CF6cbF0fF7b4dDf41443Ea7a5C8Cc8"), +} + +func OPCMBlueprintsFor(chainID uint64) (OPCMBlueprints, error) { + switch chainID { + case 1: + return mainnetBlueprints, nil + case 11155111: + return sepoliaBlueprints, nil + default: + return OPCMBlueprints{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func L1VersionsDataFor(chainID uint64) (string, error) { + switch chainID { + case 1: + return VersionsMainnetData, nil + case 11155111: + return VersionsSepoliaData, nil + default: + return "", fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func L1VersionsFor(chainID uint64) (L1Versions, error) { + switch chainID { + case 1: + return L1VersionsMainnet, nil + case 11155111: + return L1VersionsSepolia, nil + default: + return L1Versions{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func GuardianAddressFor(chainID uint64) (common.Address, error) { + switch chainID { + case 1: + return common.HexToAddress("0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2"), nil + case 11155111: + return common.HexToAddress("0x7a50f00e8D05b95F98fE38d8BeE366a7324dCf7E"), nil + default: + return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func ChallengerAddressFor(chainID uint64) (common.Address, error) { + switch chainID { + case 1: + return common.HexToAddress("0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A"), nil + case 11155111: + return common.HexToAddress("0xfd1D2e729aE8eEe2E146c033bf4400fE75284301"), nil + default: + return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func SuperchainFor(chainID uint64) (*superchain.Superchain, error) { + switch chainID { + case 1: + return superchain.Superchains["mainnet"], nil + case 11155111: + return superchain.Superchains["sepolia"], nil + default: + return nil, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func ChainNameFor(chainID uint64) (string, error) { + switch chainID { + case 1: + return "mainnet", nil + case 11155111: + return "sepolia", nil + default: + return "", fmt.Errorf("unrecognized l1 chain ID: %d", chainID) + } +} + +func CommitForDeployTag(tag string) (string, error) { + switch tag { + case "op-contracts/v1.6.0": + return "33f06d2d5e4034125df02264a5ffe84571bd0359", nil + case "op-contracts/v1.7.0-beta.1+l2-contracts": + return "5e14a61547a45eef2ebeba677aee4a049f106ed8", nil + default: + return "", fmt.Errorf("unsupported tag: %s", tag) + } +} + +func ManagerImplementationAddrFor(chainID uint64) (common.Address, error) { + switch chainID { + case 1: + // Generated using the bootstrap command on 11/18/2024. + // Verified against compiled bytecode at: + // https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts-v160-artifacts-opcm-redesign-backport + return common.HexToAddress("0x9BC0A1eD534BFb31a6Be69e5b767Cba332f14347"), nil + case 11155111: + // Generated using the bootstrap command on 11/18/2024. + // Verified against compiled bytecode at: + // https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts-v160-artifacts-opcm-redesign-backport + return common.HexToAddress("0x760B1d2Dc68DC51fb6E8B2b8722B8ed08903540c"), nil + default: + return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func ManagerOwnerAddrFor(chainID uint64) (common.Address, error) { + switch chainID { + case 1: + // Set to superchain proxy admin + return common.HexToAddress("0x543bA4AADBAb8f9025686Bd03993043599c6fB04"), nil + case 11155111: + // Set to development multisig + return common.HexToAddress("0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B"), nil + default: + return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func SystemOwnerAddrFor(chainID uint64) (common.Address, error) { + switch chainID { + case 1: + // Set to owner of superchain proxy admin + return common.HexToAddress("0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A"), nil + case 11155111: + // Set to development multisig + return common.HexToAddress("0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B"), nil + default: + return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func L1ProxyAdminOwner(chainID uint64) (common.Address, error) { + switch chainID { + case 1: + return common.HexToAddress("0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A"), nil + case 11155111: + return common.HexToAddress("0x1Eb2fFc903729a0F03966B917003800b145F56E2"), nil + default: + return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +func ArtifactsURLForTag(tag string) (*url.URL, error) { + switch tag { + case "op-contracts/v1.6.0": + return url.Parse(standardArtifactsURL("e1f0c4020618c4a98972e7124c39686cab2e31d5d7846f9ce5e0d5eed0f5ff32")) + case "op-contracts/v1.7.0-beta.1+l2-contracts": + return url.Parse(standardArtifactsURL("b0fb1f6f674519d637cff39a22187a5993d7f81a6d7b7be6507a0b50a5e38597")) + case "op-contracts/v1.8.0-rc.3": + return url.Parse(standardArtifactsURL("3bcff2944953862596d5fd0125d166a04af2ba6426dc693983291d3cb86b2e2e")) + default: + return nil, fmt.Errorf("unsupported tag: %s", tag) + } +} + +func ArtifactsHashForTag(tag string) (common.Hash, error) { + switch tag { + case "op-contracts/v1.6.0": + return common.HexToHash("d20a930cc0ff204c2d93b7aa60755ec7859ba4f328b881f5090c6a6a2a86dcba"), nil + case "op-contracts/v1.7.0-beta.1+l2-contracts": + return common.HexToHash("9e3ad322ec9b2775d59143ce6874892f9b04781742c603ad59165159e90b00b9"), nil + case "op-contracts/v1.8.0-rc.3": + return common.HexToHash("7c133142165fbbdba28ced5d9a04af8bea68baf58b19a07cdd8ae531b01fbe9d"), nil + default: + return common.Hash{}, fmt.Errorf("unsupported tag: %s", tag) + } +} + +func standardArtifactsURL(checksum string) string { + return fmt.Sprintf("https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-%s.tar.gz", checksum) +} + +func init() { + L1VersionsMainnet = L1Versions{} + if err := toml.Unmarshal([]byte(VersionsMainnetData), &L1VersionsMainnet); err != nil { + panic(err) + } + + L1VersionsSepolia = L1Versions{} + if err := toml.Unmarshal([]byte(VersionsSepoliaData), &L1VersionsSepolia); err != nil { + panic(err) + } +} diff --git a/op-chain-ops/deployer/state/base64.go b/op-deployer/pkg/deployer/state/base64.go similarity index 100% rename from op-chain-ops/deployer/state/base64.go rename to op-deployer/pkg/deployer/state/base64.go diff --git a/op-chain-ops/deployer/state/base64_test.go b/op-deployer/pkg/deployer/state/base64_test.go similarity index 100% rename from op-chain-ops/deployer/state/base64_test.go rename to op-deployer/pkg/deployer/state/base64_test.go diff --git a/op-deployer/pkg/deployer/state/chain_intent.go b/op-deployer/pkg/deployer/state/chain_intent.go new file mode 100644 index 0000000000000..bb6693f56b882 --- /dev/null +++ b/op-deployer/pkg/deployer/state/chain_intent.go @@ -0,0 +1,82 @@ +package state + +import ( + "fmt" + "reflect" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum/go-ethereum/common" +) + +type ChainIntent struct { + ID common.Hash `json:"id" toml:"id"` + BaseFeeVaultRecipient common.Address `json:"baseFeeVaultRecipient" toml:"baseFeeVaultRecipient"` + L1FeeVaultRecipient common.Address `json:"l1FeeVaultRecipient" toml:"l1FeeVaultRecipient"` + SequencerFeeVaultRecipient common.Address `json:"sequencerFeeVaultRecipient" toml:"sequencerFeeVaultRecipient"` + Eip1559DenominatorCanyon uint64 `json:"eip1559DenominatorCanyon" toml:"eip1559DenominatorCanyon"` + Eip1559Denominator uint64 `json:"eip1559Denominator" toml:"eip1559Denominator"` + Eip1559Elasticity uint64 `json:"eip1559Elasticity" toml:"eip1559Elasticity"` + Roles ChainRoles `json:"roles" toml:"roles"` + DeployOverrides map[string]any `json:"deployOverrides" toml:"deployOverrides"` + DangerousAltDAConfig genesis.AltDADeployConfig `json:"dangerousAltDAConfig,omitempty" toml:"dangerousAltDAConfig,omitempty"` +} + +type ChainRoles struct { + L1ProxyAdminOwner common.Address `json:"l1ProxyAdminOwner" toml:"l1ProxyAdminOwner"` + L2ProxyAdminOwner common.Address `json:"l2ProxyAdminOwner" toml:"l2ProxyAdminOwner"` + SystemConfigOwner common.Address `json:"systemConfigOwner" toml:"systemConfigOwner"` + UnsafeBlockSigner common.Address `json:"unsafeBlockSigner" toml:"unsafeBlockSigner"` + Batcher common.Address `json:"batcher" toml:"batcher"` + Proposer common.Address `json:"proposer" toml:"proposer"` + Challenger common.Address `json:"challenger" toml:"challenger"` +} + +var ErrChainRoleZeroAddress = fmt.Errorf("ChainRole is set to zero address") +var ErrFeeVaultZeroAddress = fmt.Errorf("chain has a fee vault set to zero address") +var ErrNonStandardValue = fmt.Errorf("chain contains non-standard config value") +var ErrEip1559ZeroValue = fmt.Errorf("eip1559 param is set to zero value") + +func (c *ChainIntent) Check() error { + if c.ID == emptyHash { + return fmt.Errorf("id must be set") + } + + if err := c.Roles.CheckNoZeroAddresses(); err != nil { + return err + } + + if c.Eip1559DenominatorCanyon == 0 || + c.Eip1559Denominator == 0 || + c.Eip1559Elasticity == 0 { + return fmt.Errorf("%w: chainId=%s", ErrEip1559ZeroValue, c.ID) + } + if c.BaseFeeVaultRecipient == emptyAddress || + c.L1FeeVaultRecipient == emptyAddress || + c.SequencerFeeVaultRecipient == emptyAddress { + return fmt.Errorf("%w: chainId=%s", ErrFeeVaultZeroAddress, c.ID) + } + + if c.DangerousAltDAConfig.UseAltDA { + return c.DangerousAltDAConfig.Check(nil) + } + + return nil +} + +// Returns an error if any fields in ChainRoles is set to common.Address{} +func (cr *ChainRoles) CheckNoZeroAddresses() error { + val := reflect.ValueOf(*cr) + typ := reflect.TypeOf(*cr) + + // Iterate through all the fields + for i := 0; i < val.NumField(); i++ { + fieldValue := val.Field(i) + fieldName := typ.Field(i).Name + + if fieldValue.Interface() == (common.Address{}) { + return fmt.Errorf("%w: %s", ErrChainRoleZeroAddress, fieldName) + } + } + + return nil +} diff --git a/op-deployer/pkg/deployer/state/deploy_config.go b/op-deployer/pkg/deployer/state/deploy_config.go new file mode 100644 index 0000000000000..11445c2fb6984 --- /dev/null +++ b/op-deployer/pkg/deployer/state/deploy_config.go @@ -0,0 +1,189 @@ +package state + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var ( + l2GenesisBlockBaseFeePerGas = hexutil.Big(*(big.NewInt(1000000000))) + + vaultMinWithdrawalAmount = mustHexBigFromHex("0x8ac7230489e80000") +) + +func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State, chainState *ChainState) (genesis.DeployConfig, error) { + cfg := genesis.DeployConfig{ + L1DependenciesConfig: genesis.L1DependenciesConfig{ + L1StandardBridgeProxy: chainState.L1StandardBridgeProxyAddress, + L1CrossDomainMessengerProxy: chainState.L1CrossDomainMessengerProxyAddress, + L1ERC721BridgeProxy: chainState.L1ERC721BridgeProxyAddress, + SystemConfigProxy: chainState.SystemConfigProxyAddress, + OptimismPortalProxy: chainState.OptimismPortalProxyAddress, + ProtocolVersionsProxy: state.SuperchainDeployment.ProtocolVersionsProxyAddress, + }, + L2InitializationConfig: genesis.L2InitializationConfig{ + L2GenesisBlockDeployConfig: genesis.L2GenesisBlockDeployConfig{ + L2GenesisBlockGasLimit: 60_000_000, + L2GenesisBlockBaseFeePerGas: &l2GenesisBlockBaseFeePerGas, + }, + L2VaultsDeployConfig: genesis.L2VaultsDeployConfig{ + BaseFeeVaultWithdrawalNetwork: "local", + L1FeeVaultWithdrawalNetwork: "local", + SequencerFeeVaultWithdrawalNetwork: "local", + SequencerFeeVaultMinimumWithdrawalAmount: vaultMinWithdrawalAmount, + BaseFeeVaultMinimumWithdrawalAmount: vaultMinWithdrawalAmount, + L1FeeVaultMinimumWithdrawalAmount: vaultMinWithdrawalAmount, + BaseFeeVaultRecipient: chainIntent.BaseFeeVaultRecipient, + L1FeeVaultRecipient: chainIntent.L1FeeVaultRecipient, + SequencerFeeVaultRecipient: chainIntent.SequencerFeeVaultRecipient, + }, + GovernanceDeployConfig: genesis.GovernanceDeployConfig{ + EnableGovernance: true, + GovernanceTokenSymbol: "OP", + GovernanceTokenName: "Optimism", + GovernanceTokenOwner: common.HexToAddress("0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAdDEad"), + }, + GasPriceOracleDeployConfig: genesis.GasPriceOracleDeployConfig{ + GasPriceOracleBaseFeeScalar: 1368, + GasPriceOracleBlobBaseFeeScalar: 810949, + }, + EIP1559DeployConfig: genesis.EIP1559DeployConfig{ + EIP1559Denominator: chainIntent.Eip1559Denominator, + EIP1559DenominatorCanyon: 250, + EIP1559Elasticity: chainIntent.Eip1559Elasticity, + }, + + // STOP! This struct sets the _default_ upgrade schedule for all chains. + // Any upgrades you enable here will be enabled for all new deployments. + // In-development hardforks should never be activated here. Instead, they + // should be specified as overrides. + UpgradeScheduleDeployConfig: genesis.UpgradeScheduleDeployConfig{ + L2GenesisRegolithTimeOffset: u64UtilPtr(0), + L2GenesisCanyonTimeOffset: u64UtilPtr(0), + L2GenesisDeltaTimeOffset: u64UtilPtr(0), + L2GenesisEcotoneTimeOffset: u64UtilPtr(0), + L2GenesisFjordTimeOffset: u64UtilPtr(0), + L2GenesisGraniteTimeOffset: u64UtilPtr(0), + UseInterop: intent.UseInterop, + }, + L2CoreDeployConfig: genesis.L2CoreDeployConfig{ + L1ChainID: intent.L1ChainID, + L2ChainID: chainState.ID.Big().Uint64(), + L2BlockTime: 2, + FinalizationPeriodSeconds: 12, + MaxSequencerDrift: 600, + SequencerWindowSize: 3600, + ChannelTimeoutBedrock: 300, + SystemConfigStartBlock: 0, + BatchInboxAddress: calculateBatchInboxAddr(chainState.ID), + }, + OperatorDeployConfig: genesis.OperatorDeployConfig{ + BatchSenderAddress: chainIntent.Roles.Batcher, + P2PSequencerAddress: chainIntent.Roles.UnsafeBlockSigner, + }, + OwnershipDeployConfig: genesis.OwnershipDeployConfig{ + ProxyAdminOwner: chainIntent.Roles.L2ProxyAdminOwner, + FinalSystemOwner: chainIntent.Roles.L1ProxyAdminOwner, + }, + }, + FaultProofDeployConfig: genesis.FaultProofDeployConfig{ + UseFaultProofs: true, + FaultGameWithdrawalDelay: 604800, + PreimageOracleMinProposalSize: 126000, + PreimageOracleChallengePeriod: 86400, + ProofMaturityDelaySeconds: 604800, + DisputeGameFinalityDelaySeconds: 302400, + }, + } + + if intent.UseInterop { + cfg.L2InitializationConfig.UpgradeScheduleDeployConfig.L2GenesisInteropTimeOffset = u64UtilPtr(0) + } + + if chainState.StartBlock == nil { + // These are dummy variables - see below for rationale. + num := rpc.LatestBlockNumber + cfg.L1StartingBlockTag = &genesis.MarshalableRPCBlockNumberOrHash{ + BlockNumber: &num, + } + } else { + startHash := chainState.StartBlock.Hash() + cfg.L1StartingBlockTag = &genesis.MarshalableRPCBlockNumberOrHash{ + BlockHash: &startHash, + } + } + + if chainIntent.DangerousAltDAConfig.UseAltDA { + cfg.AltDADeployConfig = chainIntent.DangerousAltDAConfig + cfg.L1DependenciesConfig.DAChallengeProxy = chainState.DataAvailabilityChallengeProxyAddress + } + + // The below dummy variables are set in order to allow the deploy + // config to pass validation. The validation checks are useful to + // ensure that the L2 is properly configured. They are not used by + // the L2 genesis script itself. + + cfg.L1BlockTime = 12 + dummyAddr := common.Address{19: 0x01} + cfg.SuperchainL1DeployConfig = genesis.SuperchainL1DeployConfig{ + SuperchainConfigGuardian: dummyAddr, + } + cfg.OutputOracleDeployConfig = genesis.OutputOracleDeployConfig{ + L2OutputOracleSubmissionInterval: 1, + L2OutputOracleStartingTimestamp: 1, + L2OutputOracleProposer: dummyAddr, + L2OutputOracleChallenger: dummyAddr, + } + // End of dummy variables + + // Apply overrides after setting the main values. + var err error + if len(intent.GlobalDeployOverrides) > 0 { + cfg, err = jsonutil.MergeJSON(cfg, intent.GlobalDeployOverrides) + if err != nil { + return genesis.DeployConfig{}, fmt.Errorf("error merging global L2 overrides: %w", err) + + } + } + + if len(chainIntent.DeployOverrides) > 0 { + cfg, err = jsonutil.MergeJSON(cfg, chainIntent.DeployOverrides) + if err != nil { + return genesis.DeployConfig{}, fmt.Errorf("error merging chain L2 overrides: %w", err) + } + } + + if err := cfg.Check(log.New(log.DiscardHandler())); err != nil { + return cfg, fmt.Errorf("combined deploy config failed validation: %w", err) + } + + return cfg, nil +} + +func mustHexBigFromHex(hex string) *hexutil.Big { + num := hexutil.MustDecodeBig(hex) + hexBig := hexutil.Big(*num) + return &hexBig +} + +func u64UtilPtr(in uint64) *hexutil.Uint64 { + util := hexutil.Uint64(in) + return &util +} + +func calculateBatchInboxAddr(chainID common.Hash) common.Address { + var out common.Address + copy(out[1:], crypto.Keccak256(chainID[:])[:19]) + return out +} diff --git a/op-deployer/pkg/deployer/state/deploy_config_test.go b/op-deployer/pkg/deployer/state/deploy_config_test.go new file mode 100644 index 0000000000000..c0381507d1694 --- /dev/null +++ b/op-deployer/pkg/deployer/state/deploy_config_test.go @@ -0,0 +1,47 @@ +package state + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +func TestCombineDeployConfig(t *testing.T) { + intent := Intent{ + L1ChainID: 1, + } + chainState := ChainState{ + ID: common.HexToHash("0x123"), + } + chainIntent := ChainIntent{ + Eip1559Denominator: 1, + Eip1559Elasticity: 2, + BaseFeeVaultRecipient: common.HexToAddress("0x123"), + L1FeeVaultRecipient: common.HexToAddress("0x456"), + SequencerFeeVaultRecipient: common.HexToAddress("0x789"), + Roles: ChainRoles{ + SystemConfigOwner: common.HexToAddress("0x123"), + L1ProxyAdminOwner: common.HexToAddress("0x456"), + L2ProxyAdminOwner: common.HexToAddress("0x789"), + UnsafeBlockSigner: common.HexToAddress("0xabc"), + Batcher: common.HexToAddress("0xdef"), + }, + } + state := State{ + SuperchainDeployment: &SuperchainDeployment{ProtocolVersionsProxyAddress: common.HexToAddress("0x123")}, + } + + // apply hard fork overrides + chainIntent.DeployOverrides = map[string]any{ + "l2GenesisGraniteTimeOffset": "0x8", + "l2GenesisHoloceneTimeOffset": "0x10", + } + + out, err := CombineDeployConfig(&intent, &chainIntent, &state, &chainState) + require.NoError(t, err) + require.Equal(t, *out.L2InitializationConfig.UpgradeScheduleDeployConfig.L2GenesisFjordTimeOffset, hexutil.Uint64(0)) + require.Equal(t, *out.L2InitializationConfig.UpgradeScheduleDeployConfig.L2GenesisGraniteTimeOffset, hexutil.Uint64(8)) + require.Equal(t, *out.L2InitializationConfig.UpgradeScheduleDeployConfig.L2GenesisHoloceneTimeOffset, hexutil.Uint64(16)) +} diff --git a/op-deployer/pkg/deployer/state/gzip.go b/op-deployer/pkg/deployer/state/gzip.go new file mode 100644 index 0000000000000..4a2ee19546339 --- /dev/null +++ b/op-deployer/pkg/deployer/state/gzip.go @@ -0,0 +1,50 @@ +package state + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" +) + +type GzipData[T any] struct { + Data *T +} + +func (g *GzipData[T]) MarshalJSON() ([]byte, error) { + jsonData, err := json.Marshal(g.Data) + if err != nil { + return nil, fmt.Errorf("failed to encode json: %w", err) + } + + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + if _, err := gw.Write(jsonData); err != nil { + return nil, fmt.Errorf("failed to write gzip data: %w", err) + } + if err := gw.Close(); err != nil { + return nil, fmt.Errorf("failed to close gzip writer: %w", err) + } + + return json.Marshal(Base64Bytes(buf.Bytes())) +} + +func (g *GzipData[T]) UnmarshalJSON(b []byte) error { + var b64B Base64Bytes + if err := json.Unmarshal(b, &b64B); err != nil { + return fmt.Errorf("failed to decode gzip data: %w", err) + } + + gr, err := gzip.NewReader(bytes.NewReader(b64B)) + if err != nil { + return fmt.Errorf("failed to create gzip reader: %w", err) + } + defer gr.Close() + + var data T + if err := json.NewDecoder(gr).Decode(&data); err != nil { + return fmt.Errorf("failed to decode gzip data: %w", err) + } + g.Data = &data + return nil +} diff --git a/op-deployer/pkg/deployer/state/gzip_test.go b/op-deployer/pkg/deployer/state/gzip_test.go new file mode 100644 index 0000000000000..d7319a9088e45 --- /dev/null +++ b/op-deployer/pkg/deployer/state/gzip_test.go @@ -0,0 +1,57 @@ +package state + +import ( + "encoding/json" + "fmt" + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func TestGzipData_Marshaling(t *testing.T) { + type ts struct { + Field *GzipData[foundry.ForgeAllocs] + } + + tests := []struct { + name string + in ts + out string + }{ + { + name: "empty", + in: ts{}, + out: "null", + }, + { + name: "contains some data", + in: ts{ + Field: &GzipData[foundry.ForgeAllocs]{ + Data: &foundry.ForgeAllocs{ + Accounts: map[common.Address]types.Account{ + common.HexToAddress("0x1"): { + Balance: big.NewInt(1), + }, + }, + }, + }, + }, + out: `"H4sIAAAAAAAA/6pWMqgwIA4YKllVKyUl5iTmJacqWSkZVBgq1dYCAgAA//9hulF0QAAAAA=="`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(tt.in) + require.NoError(t, err) + require.Equal(t, fmt.Sprintf(`{"Field":%s}`, tt.out), string(data)) + var unmarshalled ts + err = json.Unmarshal(data, &unmarshalled) + require.NoError(t, err) + require.EqualValues(t, tt.in, unmarshalled) + }) + } +} diff --git a/op-deployer/pkg/deployer/state/intent.go b/op-deployer/pkg/deployer/state/intent.go new file mode 100644 index 0000000000000..5b1e4222b3e0a --- /dev/null +++ b/op-deployer/pkg/deployer/state/intent.go @@ -0,0 +1,370 @@ +package state + +import ( + "errors" + "fmt" + "math/big" + "net/url" + "reflect" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + "github.com/ethereum/go-ethereum/common" +) + +type DeploymentStrategy string + +const ( + DeploymentStrategyLive DeploymentStrategy = "live" + DeploymentStrategyGenesis DeploymentStrategy = "genesis" +) + +func (d DeploymentStrategy) Check() error { + switch d { + case DeploymentStrategyLive, DeploymentStrategyGenesis: + return nil + default: + return fmt.Errorf("deployment strategy must be 'live' or 'genesis'") + } +} + +type IntentConfigType string + +const ( + IntentConfigTypeStandard IntentConfigType = "standard" + IntentConfigTypeCustom IntentConfigType = "custom" + IntentConfigTypeStrict IntentConfigType = "strict" + IntentConfigTypeStandardOverrides IntentConfigType = "standard-overrides" + IntentConfigTypeStrictOverrides IntentConfigType = "strict-overrides" +) + +var emptyAddress common.Address +var emptyHash common.Hash + +type Intent struct { + DeploymentStrategy DeploymentStrategy `json:"deploymentStrategy" toml:"deploymentStrategy"` + ConfigType IntentConfigType `json:"configType" toml:"configType"` + L1ChainID uint64 `json:"l1ChainID" toml:"l1ChainID"` + SuperchainRoles *SuperchainRoles `json:"superchainRoles" toml:"superchainRoles,omitempty"` + FundDevAccounts bool `json:"fundDevAccounts" toml:"fundDevAccounts"` + UseInterop bool `json:"useInterop" toml:"useInterop"` + L1ContractsLocator *artifacts.Locator `json:"l1ContractsLocator" toml:"l1ContractsLocator"` + L2ContractsLocator *artifacts.Locator `json:"l2ContractsLocator" toml:"l2ContractsLocator"` + Chains []*ChainIntent `json:"chains" toml:"chains"` + GlobalDeployOverrides map[string]any `json:"globalDeployOverrides" toml:"globalDeployOverrides"` +} + +type SuperchainRoles struct { + ProxyAdminOwner common.Address `json:"proxyAdminOwner" toml:"proxyAdminOwner"` + ProtocolVersionsOwner common.Address `json:"protocolVersionsOwner" toml:"protocolVersionsOwner"` + Guardian common.Address `json:"guardian" toml:"guardian"` +} + +var ErrSuperchainRoleZeroAddress = errors.New("SuperchainRole is set to zero address") +var ErrL1ContractsLocatorUndefined = errors.New("L1ContractsLocator undefined") +var ErrL2ContractsLocatorUndefined = errors.New("L2ContractsLocator undefined") + +func (s *SuperchainRoles) CheckNoZeroAddresses() error { + val := reflect.ValueOf(*s) + typ := reflect.TypeOf(*s) + + // Iterate through all the fields + for i := 0; i < val.NumField(); i++ { + fieldValue := val.Field(i) + fieldName := typ.Field(i).Name + + if fieldValue.Interface() == (common.Address{}) { + return fmt.Errorf("%w: %s", ErrSuperchainRoleZeroAddress, fieldName) + } + } + return nil +} + +func (c *Intent) L1ChainIDBig() *big.Int { + return big.NewInt(int64(c.L1ChainID)) +} + +func (c *Intent) validateCustomConfig() error { + if c.L1ContractsLocator == nil || + (c.L1ContractsLocator.Tag == "" && c.L1ContractsLocator.URL == &url.URL{}) { + return ErrL1ContractsLocatorUndefined + } + if c.L2ContractsLocator == nil || + (c.L2ContractsLocator.Tag == "" && c.L2ContractsLocator.URL == &url.URL{}) { + return ErrL2ContractsLocatorUndefined + } + + if c.SuperchainRoles == nil { + return errors.New("SuperchainRoles is set to nil") + } + if err := c.SuperchainRoles.CheckNoZeroAddresses(); err != nil { + return err + } + + if len(c.Chains) == 0 { + return errors.New("must define at least one l2 chain") + } + + for _, chain := range c.Chains { + if err := chain.Check(); err != nil { + return err + } + } + + return nil +} + +func (c *Intent) validateStrictConfig() error { + if err := c.validateStandardValues(); err != nil { + return err + } + + challenger, _ := standard.ChallengerAddressFor(c.L1ChainID) + l1ProxyAdminOwner, _ := standard.L1ProxyAdminOwner(c.L1ChainID) + for chainIndex := range c.Chains { + if c.Chains[chainIndex].Roles.Challenger != challenger { + return fmt.Errorf("invalid challenger address for chain: %s", c.Chains[chainIndex].ID) + } + if c.Chains[chainIndex].Roles.L1ProxyAdminOwner != l1ProxyAdminOwner { + return fmt.Errorf("invalid l1ProxyAdminOwner address for chain: %s", c.Chains[chainIndex].ID) + } + } + + return nil +} + +// Ensures the following: +// 1. no zero-values for non-standard fields (user should have populated these) +// 2. no non-standard values for standard fields (user should not have changed these) +func (c *Intent) validateStandardValues() error { + if err := c.checkL1Prod(); err != nil { + return err + } + if err := c.checkL2Prod(); err != nil { + return err + } + + standardSuperchainRoles, err := getStandardSuperchainRoles(c.L1ChainID) + if err != nil { + return fmt.Errorf("error getting standard superchain roles: %w", err) + } + if c.SuperchainRoles == nil || *c.SuperchainRoles != *standardSuperchainRoles { + return fmt.Errorf("SuperchainRoles does not match standard value") + } + + for _, chain := range c.Chains { + if err := chain.Check(); err != nil { + return err + } + if chain.Eip1559DenominatorCanyon != standard.Eip1559DenominatorCanyon || + chain.Eip1559Denominator != standard.Eip1559Denominator || + chain.Eip1559Elasticity != standard.Eip1559Elasticity { + return fmt.Errorf("%w: chainId=%s", ErrNonStandardValue, chain.ID) + } + } + + return nil +} + +func getStandardSuperchainRoles(l1ChainId uint64) (*SuperchainRoles, error) { + superCfg, err := standard.SuperchainFor(l1ChainId) + if err != nil { + return nil, fmt.Errorf("error getting superchain config: %w", err) + } + + proxyAdminOwner, err := standard.L1ProxyAdminOwner(l1ChainId) + if err != nil { + return nil, fmt.Errorf("error getting L1ProxyAdminOwner: %w", err) + } + guardian, err := standard.GuardianAddressFor(l1ChainId) + if err != nil { + return nil, fmt.Errorf("error getting guardian address: %w", err) + } + + superchainRoles := &SuperchainRoles{ + ProxyAdminOwner: proxyAdminOwner, + ProtocolVersionsOwner: common.Address(*superCfg.Config.ProtocolVersionsAddr), + Guardian: guardian, + } + + return superchainRoles, nil +} + +func (c *Intent) Check() error { + if c.L1ChainID == 0 { + return fmt.Errorf("l1ChainID cannot be 0") + } + + if err := c.DeploymentStrategy.Check(); err != nil { + return err + } + + if c.L1ContractsLocator == nil { + return ErrL1ContractsLocatorUndefined + } + + if c.L2ContractsLocator == nil { + return ErrL2ContractsLocatorUndefined + } + + var err error + switch c.ConfigType { + case IntentConfigTypeStandard: + err = c.validateStandardValues() + case IntentConfigTypeCustom: + err = c.validateCustomConfig() + case IntentConfigTypeStrict: + err = c.validateStrictConfig() + case IntentConfigTypeStandardOverrides, IntentConfigTypeStrictOverrides: + err = c.validateCustomConfig() + default: + return fmt.Errorf("intent-config-type unsupported: %s", c.ConfigType) + } + if err != nil { + return fmt.Errorf("failed to validate intent-config-type=%s: %w", c.ConfigType, err) + } + + return nil +} + +func (c *Intent) Chain(id common.Hash) (*ChainIntent, error) { + for i := range c.Chains { + if c.Chains[i].ID == id { + return c.Chains[i], nil + } + } + + return nil, fmt.Errorf("chain %d not found", id) +} + +func (c *Intent) WriteToFile(path string) error { + return jsonutil.WriteTOML(c, ioutil.ToAtomicFile(path, 0o755)) +} + +func (c *Intent) checkL1Prod() error { + versions, err := standard.L1VersionsFor(c.L1ChainID) + if err != nil { + return err + } + + if _, ok := versions.Releases[c.L1ContractsLocator.Tag]; !ok { + return fmt.Errorf("tag '%s' not found in standard versions", c.L1ContractsLocator.Tag) + } + + return nil +} + +func (c *Intent) checkL2Prod() error { + _, err := standard.ArtifactsURLForTag(c.L2ContractsLocator.Tag) + return err +} + +func NewIntent(configType IntentConfigType, deploymentStrategy DeploymentStrategy, l1ChainId uint64, l2ChainIds []common.Hash) (Intent, error) { + switch configType { + case IntentConfigTypeCustom: + return NewIntentCustom(deploymentStrategy, l1ChainId, l2ChainIds) + + case IntentConfigTypeStandard: + return NewIntentStandard(deploymentStrategy, l1ChainId, l2ChainIds) + + case IntentConfigTypeStandardOverrides: + return NewIntentStandardOverrides(deploymentStrategy, l1ChainId, l2ChainIds) + + case IntentConfigTypeStrict: + return NewIntentStrict(deploymentStrategy, l1ChainId, l2ChainIds) + + case IntentConfigTypeStrictOverrides: + return NewIntentStrictOverrides(deploymentStrategy, l1ChainId, l2ChainIds) + + default: + return Intent{}, fmt.Errorf("intent config type not supported") + } +} + +// Sets all Intent fields to their zero value with the expectation that the +// user will populate the values before running 'apply' +func NewIntentCustom(deploymentStrategy DeploymentStrategy, l1ChainId uint64, l2ChainIds []common.Hash) (Intent, error) { + intent := Intent{ + DeploymentStrategy: deploymentStrategy, + ConfigType: IntentConfigTypeCustom, + L1ChainID: l1ChainId, + L1ContractsLocator: &artifacts.Locator{URL: &url.URL{}}, + L2ContractsLocator: &artifacts.Locator{URL: &url.URL{}}, + SuperchainRoles: &SuperchainRoles{}, + } + + for _, l2ChainID := range l2ChainIds { + intent.Chains = append(intent.Chains, &ChainIntent{ + ID: l2ChainID, + }) + } + return intent, nil +} + +func NewIntentStandard(deploymentStrategy DeploymentStrategy, l1ChainId uint64, l2ChainIds []common.Hash) (Intent, error) { + intent := Intent{ + DeploymentStrategy: deploymentStrategy, + ConfigType: IntentConfigTypeStandard, + L1ChainID: l1ChainId, + L1ContractsLocator: artifacts.DefaultL1ContractsLocator, + L2ContractsLocator: artifacts.DefaultL2ContractsLocator, + } + + superchainRoles, err := getStandardSuperchainRoles(l1ChainId) + if err != nil { + return Intent{}, fmt.Errorf("error getting standard superchain roles: %w", err) + } + intent.SuperchainRoles = superchainRoles + + for _, l2ChainID := range l2ChainIds { + intent.Chains = append(intent.Chains, &ChainIntent{ + ID: l2ChainID, + Eip1559DenominatorCanyon: standard.Eip1559DenominatorCanyon, + Eip1559Denominator: standard.Eip1559Denominator, + Eip1559Elasticity: standard.Eip1559Elasticity, + }) + } + return intent, nil +} + +func NewIntentStandardOverrides(deploymentStrategy DeploymentStrategy, l1ChainId uint64, l2ChainIds []common.Hash) (Intent, error) { + intent, err := NewIntentStandard(deploymentStrategy, l1ChainId, l2ChainIds) + if err != nil { + return Intent{}, err + } + intent.ConfigType = IntentConfigTypeStandardOverrides + + return intent, nil +} + +// Same as NewIntentStandard, but also sets l2 Challenger and L1ProxyAdminOwner +// addresses to standard values +func NewIntentStrict(deploymentStrategy DeploymentStrategy, l1ChainId uint64, l2ChainIds []common.Hash) (Intent, error) { + intent, err := NewIntentStandard(deploymentStrategy, l1ChainId, l2ChainIds) + if err != nil { + return Intent{}, err + } + intent.ConfigType = IntentConfigTypeStrict + + challenger, _ := standard.ChallengerAddressFor(l1ChainId) + l1ProxyAdminOwner, _ := standard.ManagerOwnerAddrFor(l1ChainId) + for chainIndex := range intent.Chains { + intent.Chains[chainIndex].Roles.Challenger = challenger + intent.Chains[chainIndex].Roles.L1ProxyAdminOwner = l1ProxyAdminOwner + } + return intent, nil +} + +func NewIntentStrictOverrides(deploymentStrategy DeploymentStrategy, l1ChainId uint64, l2ChainIds []common.Hash) (Intent, error) { + intent, err := NewIntentStrict(deploymentStrategy, l1ChainId, l2ChainIds) + if err != nil { + return Intent{}, err + } + intent.ConfigType = IntentConfigTypeStrictOverrides + + return intent, nil +} diff --git a/op-deployer/pkg/deployer/state/intent_test.go b/op-deployer/pkg/deployer/state/intent_test.go new file mode 100644 index 0000000000000..1d8c12375f88d --- /dev/null +++ b/op-deployer/pkg/deployer/state/intent_test.go @@ -0,0 +1,89 @@ +package state + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestValidateStandardValues(t *testing.T) { + intent, err := NewIntentStandard(DeploymentStrategyLive, 1, []common.Hash{common.HexToHash("0x336")}) + require.NoError(t, err) + + err = intent.Check() + require.Error(t, err) + require.ErrorIs(t, err, ErrChainRoleZeroAddress) + + setChainRoles(&intent) + err = intent.Check() + require.Error(t, err) + require.ErrorIs(t, err, ErrFeeVaultZeroAddress) + + setFeeAddresses(&intent) + err = intent.Check() + require.NoError(t, err) + + intent.Chains[0].Eip1559Denominator = 3 // set to non-standard value + err = intent.Check() + require.Error(t, err) + require.ErrorIs(t, err, ErrNonStandardValue) +} + +func TestValidateCustomValues(t *testing.T) { + intent, err := NewIntentCustom(DeploymentStrategyLive, 1, []common.Hash{common.HexToHash("0x336")}) + require.NoError(t, err) + + err = intent.Check() + require.Error(t, err) + require.ErrorIs(t, err, ErrSuperchainRoleZeroAddress) + + setSuperchainRoles(&intent) + err = intent.Check() + require.Error(t, err) + require.ErrorIs(t, err, ErrChainRoleZeroAddress) + + setChainRoles(&intent) + err = intent.Check() + require.Error(t, err) + require.ErrorIs(t, err, ErrEip1559ZeroValue) + + setEip1559Params(&intent) + err = intent.Check() + require.Error(t, err) + require.ErrorIs(t, err, ErrFeeVaultZeroAddress) + + setFeeAddresses(&intent) + err = intent.Check() + require.NoError(t, err) +} + +func setSuperchainRoles(intent *Intent) { + intent.SuperchainRoles = &SuperchainRoles{ + ProxyAdminOwner: common.HexToAddress("0xa"), + ProtocolVersionsOwner: common.HexToAddress("0xb"), + Guardian: common.HexToAddress("0xc"), + } +} + +func setEip1559Params(intent *Intent) { + intent.Chains[0].Eip1559Denominator = 5000 + intent.Chains[0].Eip1559DenominatorCanyon = 5000 + intent.Chains[0].Eip1559Elasticity = 5000 +} + +func setChainRoles(intent *Intent) { + intent.Chains[0].Roles.L1ProxyAdminOwner = common.HexToAddress("0x01") + intent.Chains[0].Roles.L2ProxyAdminOwner = common.HexToAddress("0x02") + intent.Chains[0].Roles.SystemConfigOwner = common.HexToAddress("0x03") + intent.Chains[0].Roles.UnsafeBlockSigner = common.HexToAddress("0x04") + intent.Chains[0].Roles.Batcher = common.HexToAddress("0x05") + intent.Chains[0].Roles.Proposer = common.HexToAddress("0x06") + intent.Chains[0].Roles.Challenger = common.HexToAddress("0x07") +} + +func setFeeAddresses(intent *Intent) { + intent.Chains[0].BaseFeeVaultRecipient = common.HexToAddress("0x08") + intent.Chains[0].L1FeeVaultRecipient = common.HexToAddress("0x09") + intent.Chains[0].SequencerFeeVaultRecipient = common.HexToAddress("0x0A") +} diff --git a/op-chain-ops/deployer/state/state.go b/op-deployer/pkg/deployer/state/state.go similarity index 61% rename from op-chain-ops/deployer/state/state.go rename to op-deployer/pkg/deployer/state/state.go index bc4d4c6f50e44..e17ed6184c5d3 100644 --- a/op-chain-ops/deployer/state/state.go +++ b/op-deployer/pkg/deployer/state/state.go @@ -1,9 +1,6 @@ package state import ( - "bytes" - "compress/gzip" - "encoding/json" "fmt" "github.com/ethereum/go-ethereum/core/types" @@ -40,6 +37,9 @@ type State struct { // Chains contains data about L2 chain deployments. Chains []*ChainState `json:"opChainDeployments"` + + // L1StateDump contains the complete L1 state dump of the deployment. + L1StateDump *GzipData[foundry.ForgeAllocs] `json:"l1StateDump"` } func (s *State) WriteToFile(path string) error { @@ -56,27 +56,25 @@ func (s *State) Chain(id common.Hash) (*ChainState, error) { } type SuperchainDeployment struct { - ProxyAdminAddress common.Address `json:"proxyAdminAddress"` - SuperchainConfigProxyAddress common.Address `json:"superchainConfigProxyAddress"` - SuperchainConfigImplAddress common.Address `json:"superchainConfigImplAddress"` - ProtocolVersionsProxyAddress common.Address `json:"protocolVersionsProxyAddress"` - ProtocolVersionsImplAddress common.Address `json:"protocolVersionsImplAddress"` - StateDump *foundry.ForgeAllocs `json:"-"` + ProxyAdminAddress common.Address `json:"proxyAdminAddress"` + SuperchainConfigProxyAddress common.Address `json:"superchainConfigProxyAddress"` + SuperchainConfigImplAddress common.Address `json:"superchainConfigImplAddress"` + ProtocolVersionsProxyAddress common.Address `json:"protocolVersionsProxyAddress"` + ProtocolVersionsImplAddress common.Address `json:"protocolVersionsImplAddress"` } type ImplementationsDeployment struct { - OpcmProxyAddress common.Address `json:"opcmProxyAddress"` - DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` - OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` - PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` - MipsSingletonAddress common.Address `json:"mipsSingletonAddress"` - SystemConfigImplAddress common.Address `json:"systemConfigImplAddress"` - L1CrossDomainMessengerImplAddress common.Address `json:"l1CrossDomainMessengerImplAddress"` - L1ERC721BridgeImplAddress common.Address `json:"l1ERC721BridgeImplAddress"` - L1StandardBridgeImplAddress common.Address `json:"l1StandardBridgeImplAddress"` - OptimismMintableERC20FactoryImplAddress common.Address `json:"optimismMintableERC20FactoryImplAddress"` - DisputeGameFactoryImplAddress common.Address `json:"disputeGameFactoryImplAddress"` - StateDump *foundry.ForgeAllocs `json:"-"` + OpcmAddress common.Address `json:"opcmAddress"` + DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` + OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` + PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` + MipsSingletonAddress common.Address `json:"mipsSingletonAddress"` + SystemConfigImplAddress common.Address `json:"systemConfigImplAddress"` + L1CrossDomainMessengerImplAddress common.Address `json:"l1CrossDomainMessengerImplAddress"` + L1ERC721BridgeImplAddress common.Address `json:"l1ERC721BridgeImplAddress"` + L1StandardBridgeImplAddress common.Address `json:"l1StandardBridgeImplAddress"` + OptimismMintableERC20FactoryImplAddress common.Address `json:"optimismMintableERC20FactoryImplAddress"` + DisputeGameFactoryImplAddress common.Address `json:"disputeGameFactoryImplAddress"` } type ChainState struct { @@ -97,23 +95,10 @@ type ChainState struct { PermissionedDisputeGameAddress common.Address `json:"permissionedDisputeGameAddress"` DelayedWETHPermissionedGameProxyAddress common.Address `json:"delayedWETHPermissionedGameProxyAddress"` DelayedWETHPermissionlessGameProxyAddress common.Address `json:"delayedWETHPermissionlessGameProxyAddress"` + DataAvailabilityChallengeProxyAddress common.Address `json:"dataAvailabilityChallengeProxyAddress"` + DataAvailabilityChallengeImplAddress common.Address `json:"dataAvailabilityChallengeImplAddress"` - Allocs Base64Bytes `json:"allocs"` + Allocs *GzipData[foundry.ForgeAllocs] `json:"allocs"` StartBlock *types.Header `json:"startBlock"` } - -func (c *ChainState) UnmarshalAllocs() (*foundry.ForgeAllocs, error) { - gr, err := gzip.NewReader(bytes.NewReader(c.Allocs)) - if err != nil { - return nil, fmt.Errorf("failed to create gzip reader: %w", err) - } - defer gr.Close() - - var allocs foundry.ForgeAllocs - if err := json.NewDecoder(gr).Decode(&allocs); err != nil { - return nil, fmt.Errorf("failed to decode allocs: %w", err) - } - - return &allocs, nil -} diff --git a/op-deployer/pkg/deployer/testutil/env.go b/op-deployer/pkg/deployer/testutil/env.go new file mode 100644 index 0000000000000..6b289c4503b71 --- /dev/null +++ b/op-deployer/pkg/deployer/testutil/env.go @@ -0,0 +1,37 @@ +package testutil + +import ( + "context" + "fmt" + "net/url" + "path" + "runtime" + "testing" + + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/stretchr/testify/require" +) + +func LocalArtifacts(t *testing.T) (*artifacts2.Locator, foundry.StatDirFs) { + _, testFilename, _, ok := runtime.Caller(0) + require.Truef(t, ok, "failed to get test filename") + monorepoDir, err := op_service.FindMonorepoRoot(testFilename) + require.NoError(t, err) + artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts") + artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir)) + require.NoError(t, err) + loc := &artifacts2.Locator{ + URL: artifactsURL, + } + + artifactsFS, cleanupArtifacts, err := artifacts2.Download(context.Background(), loc, artifacts2.NoopDownloadProgressor) + require.NoError(t, err) + t.Cleanup(func() { + _ = cleanupArtifacts() + }) + + return loc, artifactsFS +} diff --git a/op-chain-ops/deployer/version/version.go b/op-deployer/pkg/deployer/version/version.go similarity index 100% rename from op-chain-ops/deployer/version/version.go rename to op-deployer/pkg/deployer/version/version.go diff --git a/op-deployer/pkg/env/host.go b/op-deployer/pkg/env/host.go new file mode 100644 index 0000000000000..47389f4923297 --- /dev/null +++ b/op-deployer/pkg/env/host.go @@ -0,0 +1,91 @@ +package env + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +func DefaultScriptHost( + bcaster broadcaster.Broadcaster, + lgr log.Logger, + deployer common.Address, + artifacts foundry.StatDirFs, + additionalOpts ...script.HostOption, +) (*script.Host, error) { + scriptCtx := script.DefaultContext + scriptCtx.Sender = deployer + scriptCtx.Origin = deployer + h := script.NewHost( + lgr, + &foundry.ArtifactsFS{FS: artifacts}, + nil, + scriptCtx, + append([]script.HostOption{ + script.WithBroadcastHook(bcaster.Hook), + script.WithIsolatedBroadcasts(), + script.WithCreate2Deployer(), + }, additionalOpts...)..., + ) + + if err := h.EnableCheats(); err != nil { + return nil, fmt.Errorf("failed to enable cheats: %w", err) + } + + return h, nil +} + +func DefaultForkedScriptHost( + ctx context.Context, + bcaster broadcaster.Broadcaster, + lgr log.Logger, + deployer common.Address, + artifacts foundry.StatDirFs, + forkRPC *rpc.Client, + additionalOpts ...script.HostOption, +) (*script.Host, error) { + h, err := DefaultScriptHost( + bcaster, + lgr, + deployer, + artifacts, + append([]script.HostOption{ + script.WithForkHook(func(cfg *script.ForkConfig) (forking.ForkSource, error) { + src, err := forking.RPCSourceByNumber(cfg.URLOrAlias, forkRPC, *cfg.BlockNumber) + if err != nil { + return nil, fmt.Errorf("failed to create RPC fork source: %w", err) + } + return forking.Cache(src), nil + }), + }, additionalOpts...)..., + ) + if err != nil { + return nil, fmt.Errorf("failed to create default script host: %w", err) + } + + client := ethclient.NewClient(forkRPC) + + latest, err := client.HeaderByNumber(ctx, nil) + if err != nil { + return nil, fmt.Errorf("failed to get latest block: %w", err) + } + + if _, err := h.CreateSelectFork( + script.ForkWithURLOrAlias("main"), + script.ForkWithBlockNumberU256(latest.Number), + ); err != nil { + return nil, fmt.Errorf("failed to select fork: %w", err) + } + + return h, nil +} diff --git a/op-dispute-mon/Makefile b/op-dispute-mon/Makefile index d94a0fa95a96c..6bb994650bfc3 100644 --- a/op-dispute-mon/Makefile +++ b/op-dispute-mon/Makefile @@ -1,21 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -VERSION ?= v0.0.0 +DEPRECATED_TARGETS := op-dispute-mon clean test -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-dispute-mon/version.Version=$(VERSION) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-dispute-mon/version.Meta=$(VERSION_META) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -op-dispute-mon: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-dispute-mon ./cmd -.PHONY: op-dispute-mon - -clean: - rm bin/op-dispute-mon -.PHONY: clean - -test: - go test -v ./... -.PHONY: test +include ../just/deprecated.mk diff --git a/op-dispute-mon/justfile b/op-dispute-mon/justfile new file mode 100644 index 0000000000000..3788cbd2b14ad --- /dev/null +++ b/op-dispute-mon/justfile @@ -0,0 +1,21 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X github.com/ethereum-optimism/optimism/op-dispute-mon/version.Version=" + VERSION + " " + \ + "-X github.com/ethereum-optimism/optimism/op-dispute-mon/version.Meta=" + VERSION_META + " " + \ + "") + "'" + +BINARY := "./bin/op-dispute-mon" + +# Build op-dispute-mon binary +op-dispute-mon: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") diff --git a/op-dispute-mon/metrics/metrics.go b/op-dispute-mon/metrics/metrics.go index b76c23c4ca268..aa8cd99479051 100644 --- a/op-dispute-mon/metrics/metrics.go +++ b/op-dispute-mon/metrics/metrics.go @@ -183,6 +183,8 @@ type Metricer interface { RecordL2Challenges(agreement bool, count int) + RecordOldestGameUpdateTime(t time.Time) + caching.Metrics contractMetrics.ContractMetricer } @@ -215,7 +217,8 @@ type Metrics struct { credits prometheus.GaugeVec honestWithdrawableAmounts prometheus.GaugeVec - lastOutputFetch prometheus.Gauge + lastOutputFetch prometheus.Gauge + oldestGameUpdateTime prometheus.Gauge gamesAgreement prometheus.GaugeVec latestValidProposalL2Block prometheus.Gauge @@ -269,6 +272,12 @@ func NewMetrics() *Metrics { Name: "last_output_fetch", Help: "Timestamp of the last output fetch", }), + oldestGameUpdateTime: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: Namespace, + Name: "oldest_game_update_time", + Help: "Timestamp the least recently updated game " + + "or the time of the last update cycle if there were no games in the monitoring window", + }), honestActorClaims: *factory.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "honest_actor_claims", @@ -499,6 +508,10 @@ func (m *Metrics) RecordOutputFetchTime(timestamp float64) { m.lastOutputFetch.Set(timestamp) } +func (m *Metrics) RecordOldestGameUpdateTime(t time.Time) { + m.oldestGameUpdateTime.Set(float64(t.Unix())) +} + func (m *Metrics) RecordGameAgreement(status GameAgreementStatus, count int) { m.gamesAgreement.WithLabelValues(labelValuesFor(status)...).Set(float64(count)) } diff --git a/op-dispute-mon/metrics/noop.go b/op-dispute-mon/metrics/noop.go index 6b4982ff26ba3..4459fdd9c1fbb 100644 --- a/op-dispute-mon/metrics/noop.go +++ b/op-dispute-mon/metrics/noop.go @@ -36,6 +36,8 @@ func (*NoopMetricsImpl) RecordWithdrawalRequests(_ common.Address, _ bool, _ int func (*NoopMetricsImpl) RecordOutputFetchTime(_ float64) {} +func (*NoopMetricsImpl) RecordOldestGameUpdateTime(_ time.Time) {} + func (*NoopMetricsImpl) RecordGameAgreement(_ GameAgreementStatus, _ int) {} func (*NoopMetricsImpl) RecordLatestValidProposalL2Block(_ uint64) {} diff --git a/op-dispute-mon/mon/extract/caller.go b/op-dispute-mon/mon/extract/caller.go index 03c130cd3dc88..0b88360b69aeb 100644 --- a/op-dispute-mon/mon/extract/caller.go +++ b/op-dispute-mon/mon/extract/caller.go @@ -55,7 +55,8 @@ func (g *GameCallerCreator) CreateContract(ctx context.Context, game gameTypes.G faultTypes.PermissionedGameType, faultTypes.AsteriscGameType, faultTypes.AlphabetGameType, - faultTypes.FastGameType: + faultTypes.FastGameType, + faultTypes.AsteriscKonaGameType: fdg, err := contracts.NewFaultDisputeGameContract(ctx, g.m, game.Proxy, g.caller) if err != nil { return nil, fmt.Errorf("failed to create fault dispute game contract: %w", err) diff --git a/op-dispute-mon/mon/extract/caller_test.go b/op-dispute-mon/mon/extract/caller_test.go index 065f06c051ac0..61c7f349c82b8 100644 --- a/op-dispute-mon/mon/extract/caller_test.go +++ b/op-dispute-mon/mon/extract/caller_test.go @@ -47,10 +47,14 @@ func TestMetadataCreator_CreateContract(t *testing.T) { name: "validFastGameType", game: types.GameMetadata{GameType: uint32(faultTypes.FastGameType), Proxy: fdgAddr}, }, + { + name: "validAsteriscKonaGameType", + game: types.GameMetadata{GameType: uint32(faultTypes.AsteriscKonaGameType), Proxy: fdgAddr}, + }, { name: "InvalidGameType", - game: types.GameMetadata{GameType: 3, Proxy: fdgAddr}, - expectedErr: fmt.Errorf("unsupported game type: 3"), + game: types.GameMetadata{GameType: 4, Proxy: fdgAddr}, + expectedErr: fmt.Errorf("unsupported game type: 4"), }, } diff --git a/op-dispute-mon/mon/extract/extractor.go b/op-dispute-mon/mon/extract/extractor.go index d19cab340b66b..c40b31ccb6c51 100644 --- a/op-dispute-mon/mon/extract/extractor.go +++ b/op-dispute-mon/mon/extract/extractor.go @@ -9,9 +9,11 @@ import ( gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/maps" ) var ( @@ -29,20 +31,23 @@ type Enricher interface { type Extractor struct { logger log.Logger + clock clock.Clock createContract CreateGameCaller fetchGames FactoryGameFetcher maxConcurrency int enrichers []Enricher ignoredGames map[common.Address]bool + latestGameData map[common.Address]*monTypes.EnrichedGameData } -func NewExtractor(logger log.Logger, creator CreateGameCaller, fetchGames FactoryGameFetcher, ignoredGames []common.Address, maxConcurrency uint, enrichers ...Enricher) *Extractor { +func NewExtractor(logger log.Logger, cl clock.Clock, creator CreateGameCaller, fetchGames FactoryGameFetcher, ignoredGames []common.Address, maxConcurrency uint, enrichers ...Enricher) *Extractor { ignored := make(map[common.Address]bool) for _, game := range ignoredGames { ignored[game] = true } return &Extractor{ logger: logger, + clock: cl, createContract: creator, fetchGames: fetchGames, maxConcurrency: int(maxConcurrency), @@ -61,7 +66,6 @@ func (e *Extractor) Extract(ctx context.Context, blockHash common.Hash, minTimes } func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, games []gameTypes.GameMetadata) ([]*monTypes.EnrichedGameData, int, int) { - var enrichedGames []*monTypes.EnrichedGameData var ignored atomic.Int32 var failed atomic.Int32 @@ -101,8 +105,14 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game }() } - // Push each game into the channel + // Create a new store for game data. This ensures any games no longer in the monitoring set are dropped. + updatedGameData := make(map[common.Address]*monTypes.EnrichedGameData) + // Push each game into the channel and store the latest cached game data as a default if fetching fails for _, game := range games { + previousData := e.latestGameData[game.Proxy] + if previousData != nil { + updatedGameData[game.Proxy] = previousData + } gameCh <- game } close(gameCh) @@ -112,9 +122,10 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game // Read the results for enrichedGame := range enrichedCh { - enrichedGames = append(enrichedGames, enrichedGame) + updatedGameData[enrichedGame.Proxy] = enrichedGame } - return enrichedGames, int(ignored.Load()), int(failed.Load()) + e.latestGameData = updatedGameData + return maps.Values(updatedGameData), int(ignored.Load()), int(failed.Load()) } func (e *Extractor) enrichGame(ctx context.Context, blockHash common.Hash, game gameTypes.GameMetadata) (*monTypes.EnrichedGameData, error) { @@ -138,6 +149,7 @@ func (e *Extractor) enrichGame(ctx context.Context, blockHash common.Hash, game enrichedClaims[i] = monTypes.EnrichedClaim{Claim: claim} } enrichedGame := &monTypes.EnrichedGameData{ + LastUpdateTime: e.clock.Now(), GameMetadata: game, L1Head: meta.L1Head, L2BlockNumber: meta.L2BlockNum, diff --git a/op-dispute-mon/mon/extract/extractor_test.go b/op-dispute-mon/mon/extract/extractor_test.go index 361aeb57420e3..cc0605714a04a 100644 --- a/op-dispute-mon/mon/extract/extractor_test.go +++ b/op-dispute-mon/mon/extract/extractor_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/stretchr/testify/require" @@ -26,7 +27,7 @@ var ( func TestExtractor_Extract(t *testing.T) { t.Run("FetchGamesError", func(t *testing.T) { - extractor, _, games, _ := setupExtractorTest(t) + extractor, _, games, _, _ := setupExtractorTest(t) games.err = errors.New("boom") _, _, _, err := extractor.Extract(context.Background(), common.Hash{}, 0) require.ErrorIs(t, err, games.err) @@ -34,7 +35,7 @@ func TestExtractor_Extract(t *testing.T) { }) t.Run("CreateGameErrorLog", func(t *testing.T) { - extractor, creator, games, logs := setupExtractorTest(t) + extractor, creator, games, logs, _ := setupExtractorTest(t) games.games = []gameTypes.GameMetadata{{}} creator.err = errors.New("boom") enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) @@ -50,7 +51,7 @@ func TestExtractor_Extract(t *testing.T) { }) t.Run("MetadataFetchErrorLog", func(t *testing.T) { - extractor, creator, games, logs := setupExtractorTest(t) + extractor, creator, games, logs, _ := setupExtractorTest(t) games.games = []gameTypes.GameMetadata{{}} creator.caller.metadataErr = errors.New("boom") enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) @@ -66,7 +67,7 @@ func TestExtractor_Extract(t *testing.T) { }) t.Run("ClaimsFetchErrorLog", func(t *testing.T) { - extractor, creator, games, logs := setupExtractorTest(t) + extractor, creator, games, logs, _ := setupExtractorTest(t) games.games = []gameTypes.GameMetadata{{}} creator.caller.claimsErr = errors.New("boom") enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) @@ -82,7 +83,7 @@ func TestExtractor_Extract(t *testing.T) { }) t.Run("Success", func(t *testing.T) { - extractor, creator, games, _ := setupExtractorTest(t) + extractor, creator, games, _, _ := setupExtractorTest(t) games.games = []gameTypes.GameMetadata{{}} enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) require.NoError(t, err) @@ -97,7 +98,7 @@ func TestExtractor_Extract(t *testing.T) { t.Run("EnricherFails", func(t *testing.T) { enricher := &mockEnricher{err: errors.New("whoops")} - extractor, _, games, logs := setupExtractorTest(t, enricher) + extractor, _, games, logs, _ := setupExtractorTest(t, enricher) games.games = []gameTypes.GameMetadata{{}} enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) require.NoError(t, err) @@ -110,7 +111,7 @@ func TestExtractor_Extract(t *testing.T) { t.Run("EnricherSuccess", func(t *testing.T) { enricher := &mockEnricher{} - extractor, _, games, _ := setupExtractorTest(t, enricher) + extractor, _, games, _, _ := setupExtractorTest(t, enricher) games.games = []gameTypes.GameMetadata{{}} enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) require.NoError(t, err) @@ -123,8 +124,8 @@ func TestExtractor_Extract(t *testing.T) { t.Run("MultipleEnrichersMultipleGames", func(t *testing.T) { enricher1 := &mockEnricher{} enricher2 := &mockEnricher{} - extractor, _, games, _ := setupExtractorTest(t, enricher1, enricher2) - games.games = []gameTypes.GameMetadata{{}, {}} + extractor, _, games, _, _ := setupExtractorTest(t, enricher1, enricher2) + games.games = []gameTypes.GameMetadata{{Proxy: common.Address{0xaa}}, {Proxy: common.Address{0xbb}}} enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) require.NoError(t, err) require.Zero(t, ignored) @@ -136,7 +137,7 @@ func TestExtractor_Extract(t *testing.T) { t.Run("IgnoreGames", func(t *testing.T) { enricher1 := &mockEnricher{} - extractor, _, games, logs := setupExtractorTest(t, enricher1) + extractor, _, games, logs, _ := setupExtractorTest(t, enricher1) // Two games, one of which is ignored games.games = []gameTypes.GameMetadata{{Proxy: ignoredGames[0]}, {Proxy: common.Address{0xaa}}} enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) @@ -152,6 +153,61 @@ func TestExtractor_Extract(t *testing.T) { testlog.NewMessageFilter("Ignoring game"), testlog.NewAttributesFilter("game", ignoredGames[0].Hex()))) }) + + t.Run("UseCachedValueOnFailure", func(t *testing.T) { + enricher := &mockEnricher{ + action: func(game *monTypes.EnrichedGameData) error { + game.Status = gameTypes.GameStatusDefenderWon + return nil + }, + } + extractor, _, games, _, cl := setupExtractorTest(t, enricher) + gameA := common.Address{0xaa} + gameB := common.Address{0xbb} + games.games = []gameTypes.GameMetadata{{Proxy: gameA}, {Proxy: gameB}} + + // First fetch succeeds and the results should be cached + enriched, ignored, failed, err := extractor.Extract(context.Background(), common.Hash{}, 0) + require.NoError(t, err) + require.Zero(t, ignored) + require.Zero(t, failed) + require.Len(t, enriched, 2) + require.Equal(t, 2, enricher.calls) + firstUpdateTime := cl.Now() + // All results should have current LastUpdateTime + for _, data := range enriched { + require.Equal(t, firstUpdateTime, data.LastUpdateTime) + } + + cl.AdvanceTime(2 * time.Minute) + secondUpdateTime := cl.Now() + enricher.action = func(game *monTypes.EnrichedGameData) error { + if game.Proxy == gameA { + return errors.New("boom") + } + // Updated games will have a different status + game.Status = gameTypes.GameStatusChallengerWon + return nil + } + // Second fetch fails for one of the two games, it's cached value should be used. + enriched, ignored, failed, err = extractor.Extract(context.Background(), common.Hash{}, 0) + require.NoError(t, err) + require.Zero(t, ignored) + require.Equal(t, 1, failed) + require.Len(t, enriched, 2) + require.Equal(t, 4, enricher.calls) + // The returned games are not in a fixed order, create a map to look up the game we need to assert + actual := make(map[common.Address]*monTypes.EnrichedGameData) + for _, data := range enriched { + actual[data.Proxy] = data + } + require.Contains(t, actual, gameA) + require.Contains(t, actual, gameB) + require.Equal(t, actual[gameA].Status, gameTypes.GameStatusDefenderWon) // Uses cached value from game A + require.Equal(t, actual[gameB].Status, gameTypes.GameStatusChallengerWon) // Updates game B + require.Equal(t, firstUpdateTime, actual[gameA].LastUpdateTime) + require.Equal(t, secondUpdateTime, actual[gameB].LastUpdateTime) + }) } func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr, metadataErr, claimsErr, durationErr int) { @@ -170,20 +226,22 @@ func verifyLogs(t *testing.T, logs *testlog.CapturingHandler, createErr, metadat require.Len(t, l, durationErr) } -func setupExtractorTest(t *testing.T, enrichers ...Enricher) (*Extractor, *mockGameCallerCreator, *mockGameFetcher, *testlog.CapturingHandler) { +func setupExtractorTest(t *testing.T, enrichers ...Enricher) (*Extractor, *mockGameCallerCreator, *mockGameFetcher, *testlog.CapturingHandler, *clock.DeterministicClock) { logger, capturedLogs := testlog.CaptureLogger(t, log.LvlDebug) games := &mockGameFetcher{} caller := &mockGameCaller{rootClaim: mockRootClaim} creator := &mockGameCallerCreator{caller: caller} + cl := clock.NewDeterministicClock(time.Unix(48294294, 58)) extractor := NewExtractor( logger, + cl, creator.CreateGameCaller, games.FetchGames, ignoredGames, 5, enrichers..., ) - return extractor, creator, games, capturedLogs + return extractor, creator, games, capturedLogs, cl } type mockGameFetcher struct { @@ -311,11 +369,15 @@ func (m *mockGameCaller) IsResolved(_ context.Context, _ rpcblock.Block, claims } type mockEnricher struct { - err error - calls int + err error + calls int + action func(game *monTypes.EnrichedGameData) error } -func (m *mockEnricher) Enrich(_ context.Context, _ rpcblock.Block, _ GameCaller, _ *monTypes.EnrichedGameData) error { +func (m *mockEnricher) Enrich(_ context.Context, _ rpcblock.Block, _ GameCaller, game *monTypes.EnrichedGameData) error { m.calls++ + if m.action != nil { + return m.action(game) + } return m.err } diff --git a/op-dispute-mon/mon/extract/head_enricher.go b/op-dispute-mon/mon/extract/head_enricher.go index 943e648f771df..4162c9e896d6a 100644 --- a/op-dispute-mon/mon/extract/head_enricher.go +++ b/op-dispute-mon/mon/extract/head_enricher.go @@ -5,15 +5,15 @@ import ( "fmt" monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" ) var _ Enricher = (*L1HeadBlockNumEnricher)(nil) type BlockFetcher interface { - HeaderByHash(ctx context.Context, block common.Hash) (*types.Header, error) + L1BlockRefByHash(ctx context.Context, block common.Hash) (eth.L1BlockRef, error) } type L1HeadBlockNumEnricher struct { @@ -25,10 +25,10 @@ func NewL1HeadBlockNumEnricher(client BlockFetcher) *L1HeadBlockNumEnricher { } func (e *L1HeadBlockNumEnricher) Enrich(ctx context.Context, _ rpcblock.Block, _ GameCaller, game *monTypes.EnrichedGameData) error { - header, err := e.client.HeaderByHash(ctx, game.L1Head) + header, err := e.client.L1BlockRefByHash(ctx, game.L1Head) if err != nil { return fmt.Errorf("failed to retrieve header for L1 head block %v: %w", game.L1Head, err) } - game.L1HeadNum = header.Number.Uint64() + game.L1HeadNum = header.Number return nil } diff --git a/op-dispute-mon/mon/extract/head_enricher_test.go b/op-dispute-mon/mon/extract/head_enricher_test.go index c0cb03b86b7a3..3c54c09516a70 100644 --- a/op-dispute-mon/mon/extract/head_enricher_test.go +++ b/op-dispute-mon/mon/extract/head_enricher_test.go @@ -3,13 +3,12 @@ package extract import ( "context" "errors" - "math/big" "testing" "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/common" - gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" ) @@ -39,11 +38,11 @@ type stubBlockFetcher struct { err error } -func (s *stubBlockFetcher) HeaderByHash(_ context.Context, _ common.Hash) (*gethTypes.Header, error) { +func (s *stubBlockFetcher) L1BlockRefByHash(_ context.Context, _ common.Hash) (eth.L1BlockRef, error) { if s.err != nil { - return nil, s.err + return eth.L1BlockRef{}, s.err } - return &gethTypes.Header{ - Number: new(big.Int).SetUint64(s.num), + return eth.L1BlockRef{ + Number: s.num, }, nil } diff --git a/op-dispute-mon/mon/monitor.go b/op-dispute-mon/mon/monitor.go index 039255608c73e..3fb14525e24e5 100644 --- a/op-dispute-mon/mon/monitor.go +++ b/op-dispute-mon/mon/monitor.go @@ -3,22 +3,19 @@ package mon import ( "context" "fmt" - "math/big" "time" "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) type ForecastResolution func(games []*types.EnrichedGameData, ignoredCount, failedCount int) -type Bonds func(games []*types.EnrichedGameData) -type Resolutions func(games []*types.EnrichedGameData) type Monitor func(games []*types.EnrichedGameData) -type BlockHashFetcher func(ctx context.Context, number *big.Int) (common.Hash, error) -type BlockNumberFetcher func(ctx context.Context) (uint64, error) +type HeadBlockFetcher func(ctx context.Context) (eth.L1BlockRef, error) type Extract func(ctx context.Context, blockHash common.Hash, minTimestamp uint64) ([]*types.EnrichedGameData, int, int, error) type MonitorMetrics interface { @@ -37,15 +34,10 @@ type gameMonitor struct { gameWindow time.Duration monitorInterval time.Duration - forecast ForecastResolution - bonds Bonds - resolutions Resolutions - claims Monitor - withdrawals Monitor - l2Challenges Monitor - extract Extract - fetchBlockHash BlockHashFetcher - fetchBlockNumber BlockNumberFetcher + forecast ForecastResolution + monitors []Monitor + extract Extract + fetchHeadBlock HeadBlockFetcher } func newGameMonitor( @@ -55,61 +47,50 @@ func newGameMonitor( metrics MonitorMetrics, monitorInterval time.Duration, gameWindow time.Duration, - forecast ForecastResolution, - bonds Bonds, - resolutions Resolutions, - claims Monitor, - withdrawals Monitor, - l2Challenges Monitor, + fetchHeadBlock HeadBlockFetcher, extract Extract, - fetchBlockNumber BlockNumberFetcher, - fetchBlockHash BlockHashFetcher, -) *gameMonitor { + forecast ForecastResolution, + monitors ...Monitor) *gameMonitor { return &gameMonitor{ - logger: logger, - clock: cl, - ctx: ctx, - done: make(chan struct{}), - metrics: metrics, - monitorInterval: monitorInterval, - gameWindow: gameWindow, - forecast: forecast, - bonds: bonds, - resolutions: resolutions, - claims: claims, - withdrawals: withdrawals, - l2Challenges: l2Challenges, - extract: extract, - fetchBlockNumber: fetchBlockNumber, - fetchBlockHash: fetchBlockHash, + logger: logger, + clock: cl, + ctx: ctx, + done: make(chan struct{}), + metrics: metrics, + monitorInterval: monitorInterval, + gameWindow: gameWindow, + forecast: forecast, + monitors: monitors, + extract: extract, + fetchHeadBlock: fetchHeadBlock, } } func (m *gameMonitor) monitorGames() error { start := m.clock.Now() - blockNumber, err := m.fetchBlockNumber(m.ctx) + headBlock, err := m.fetchHeadBlock(m.ctx) if err != nil { return fmt.Errorf("failed to fetch block number: %w", err) } - m.logger.Debug("Fetched block number", "blockNumber", blockNumber) - blockHash, err := m.fetchBlockHash(context.Background(), new(big.Int).SetUint64(blockNumber)) - if err != nil { - return fmt.Errorf("failed to fetch block hash: %w", err) - } + m.logger.Debug("Fetched current head block", "block", headBlock) minGameTimestamp := clock.MinCheckedTimestamp(m.clock, m.gameWindow) - enrichedGames, ignored, failed, err := m.extract(m.ctx, blockHash, minGameTimestamp) + enrichedGames, ignored, failed, err := m.extract(m.ctx, headBlock.Hash, minGameTimestamp) if err != nil { return fmt.Errorf("failed to load games: %w", err) } - m.resolutions(enrichedGames) m.forecast(enrichedGames, ignored, failed) - m.bonds(enrichedGames) - m.claims(enrichedGames) - m.withdrawals(enrichedGames) - m.l2Challenges(enrichedGames) + for _, monitor := range m.monitors { + monitor(enrichedGames) + } timeTaken := m.clock.Since(start) m.metrics.RecordMonitorDuration(timeTaken) - m.logger.Info("Completed monitoring update", "blockNumber", blockNumber, "blockHash", blockHash, "duration", timeTaken, "games", len(enrichedGames), "ignored", ignored, "failed", failed) + m.logger.Info("Completed monitoring update", + "blockNumber", headBlock.Number, + "blockHash", headBlock.Hash, + "duration", timeTaken, + "games", len(enrichedGames), + "ignored", ignored, + "failed", failed) return nil } diff --git a/op-dispute-mon/mon/monitor_test.go b/op-dispute-mon/mon/monitor_test.go index 180c29bb26ce6..2161e6af14ca5 100644 --- a/op-dispute-mon/mon/monitor_test.go +++ b/op-dispute-mon/mon/monitor_test.go @@ -3,7 +3,6 @@ package mon import ( "context" "errors" - "math/big" "testing" "time" @@ -11,6 +10,7 @@ import ( "github.com/ethereum-optimism/optimism/op-dispute-mon/metrics" monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" "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/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -24,50 +24,36 @@ var ( func TestMonitor_MonitorGames(t *testing.T) { t.Parallel() - t.Run("FailedFetchBlocknumber", func(t *testing.T) { - monitor, _, _, _, _, _, _, _ := setupMonitorTest(t) + t.Run("FailedFetchHeadBlock", func(t *testing.T) { + monitor, _, _, _ := setupMonitorTest(t) boom := errors.New("boom") - monitor.fetchBlockNumber = func(ctx context.Context) (uint64, error) { - return 0, boom - } - err := monitor.monitorGames() - require.ErrorIs(t, err, boom) - }) - - t.Run("FailedFetchBlockHash", func(t *testing.T) { - monitor, _, _, _, _, _, _, _ := setupMonitorTest(t) - boom := errors.New("boom") - monitor.fetchBlockHash = func(ctx context.Context, number *big.Int) (common.Hash, error) { - return common.Hash{}, boom + monitor.fetchHeadBlock = func(ctx context.Context) (eth.L1BlockRef, error) { + return eth.L1BlockRef{}, boom } err := monitor.monitorGames() require.ErrorIs(t, err, boom) }) t.Run("MonitorsWithNoGames", func(t *testing.T) { - monitor, factory, forecast, bonds, withdrawals, resolutions, claims, l2Challenges := setupMonitorTest(t) + monitor, factory, forecast, monitors := setupMonitorTest(t) factory.games = []*monTypes.EnrichedGameData{} err := monitor.monitorGames() require.NoError(t, err) require.Equal(t, 1, forecast.calls) - require.Equal(t, 1, bonds.calls) - require.Equal(t, 1, resolutions.calls) - require.Equal(t, 1, claims.calls) - require.Equal(t, 1, withdrawals.calls) - require.Equal(t, 1, l2Challenges.calls) + for _, m := range monitors { + require.Equal(t, 1, m.calls) + } }) t.Run("MonitorsMultipleGames", func(t *testing.T) { - monitor, factory, forecast, bonds, withdrawals, resolutions, claims, l2Challenges := setupMonitorTest(t) + monitor, factory, forecast, monitors := setupMonitorTest(t) factory.games = []*monTypes.EnrichedGameData{{}, {}, {}} err := monitor.monitorGames() require.NoError(t, err) require.Equal(t, 1, forecast.calls) - require.Equal(t, 1, bonds.calls) - require.Equal(t, 1, resolutions.calls) - require.Equal(t, 1, claims.calls) - require.Equal(t, 1, withdrawals.calls) - require.Equal(t, 1, l2Challenges.calls) + for _, m := range monitors { + require.Equal(t, 1, m.calls) + } }) } @@ -75,7 +61,7 @@ func TestMonitor_StartMonitoring(t *testing.T) { t.Run("MonitorsGames", func(t *testing.T) { addr1 := common.Address{0xaa} addr2 := common.Address{0xbb} - monitor, factory, forecaster, _, _, _, _, _ := setupMonitorTest(t) + monitor, factory, forecaster, _ := setupMonitorTest(t) factory.games = []*monTypes.EnrichedGameData{newEnrichedGameData(addr1, 9999), newEnrichedGameData(addr2, 9999)} factory.maxSuccess = len(factory.games) // Only allow two successful fetches @@ -88,7 +74,7 @@ func TestMonitor_StartMonitoring(t *testing.T) { }) t.Run("FailsToFetchGames", func(t *testing.T) { - monitor, factory, forecaster, _, _, _, _, _ := setupMonitorTest(t) + monitor, factory, forecaster, _ := setupMonitorTest(t) factory.fetchErr = errors.New("boom") monitor.StartMonitoring() @@ -110,50 +96,22 @@ func newEnrichedGameData(proxy common.Address, timestamp uint64) *monTypes.Enric } } -func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast, *mockBonds, *mockMonitor, *mockResolutionMonitor, *mockMonitor, *mockMonitor) { +func setupMonitorTest(t *testing.T) (*gameMonitor, *mockExtractor, *mockForecast, []*mockMonitor) { logger := testlog.Logger(t, log.LvlDebug) - fetchBlockNum := func(ctx context.Context) (uint64, error) { - return 1, nil - } - fetchBlockHash := func(ctx context.Context, number *big.Int) (common.Hash, error) { - return common.Hash{}, nil + fetchHeadBlock := func(ctx context.Context) (eth.L1BlockRef, error) { + return eth.L1BlockRef{Number: 1, Hash: common.Hash{0xaa}}, nil } monitorInterval := 100 * time.Millisecond cl := clock.NewAdvancingClock(10 * time.Millisecond) cl.Start() extractor := &mockExtractor{} forecast := &mockForecast{} - bonds := &mockBonds{} - resolutions := &mockResolutionMonitor{} - claims := &mockMonitor{} - withdrawals := &mockMonitor{} - l2Challenges := &mockMonitor{} - monitor := newGameMonitor( - context.Background(), - logger, - cl, - metrics.NoopMetrics, - monitorInterval, - 10*time.Second, - forecast.Forecast, - bonds.CheckBonds, - resolutions.CheckResolutions, - claims.Check, - withdrawals.Check, - l2Challenges.Check, - extractor.Extract, - fetchBlockNum, - fetchBlockHash, - ) - return monitor, extractor, forecast, bonds, withdrawals, resolutions, claims, l2Challenges -} - -type mockResolutionMonitor struct { - calls int -} - -func (m *mockResolutionMonitor) CheckResolutions(games []*monTypes.EnrichedGameData) { - m.calls++ + monitor1 := &mockMonitor{} + monitor2 := &mockMonitor{} + monitor3 := &mockMonitor{} + monitor := newGameMonitor(context.Background(), logger, cl, metrics.NoopMetrics, monitorInterval, 10*time.Second, fetchHeadBlock, + extractor.Extract, forecast.Forecast, monitor1.Check, monitor2.Check, monitor3.Check) + return monitor, extractor, forecast, []*mockMonitor{monitor1, monitor2, monitor3} } type mockMonitor struct { @@ -172,14 +130,6 @@ func (m *mockForecast) Forecast(_ []*monTypes.EnrichedGameData, _, _ int) { m.calls++ } -type mockBonds struct { - calls int -} - -func (m *mockBonds) CheckBonds(_ []*monTypes.EnrichedGameData) { - m.calls++ -} - type mockExtractor struct { fetchErr error calls int diff --git a/op-dispute-mon/mon/service.go b/op-dispute-mon/mon/service.go index 8ce97b7b8dd92..c44f082d3a886 100644 --- a/op-dispute-mon/mon/service.go +++ b/op-dispute-mon/mon/service.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" - "math/big" "sync/atomic" + "time" "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/bonds" "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" + rpcclient "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-dispute-mon/config" @@ -47,7 +47,9 @@ type Service struct { withdrawals *WithdrawalMonitor rollupClient *sources.RollupClient - l1Client *ethclient.Client + l1RPC rpcclient.RPC + l1Client *sources.L1Client + l1Caller *batching.MultiCaller pprofService *oppprof.Service metricsSrv *httputil.HTTPServer @@ -120,12 +122,13 @@ func (s *Service) initWithdrawalMonitor() { } func (s *Service) initGameCallerCreator() { - s.game = extract.NewGameCallerCreator(s.metrics, batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize)) + s.game = extract.NewGameCallerCreator(s.metrics, s.l1Caller) } func (s *Service) initExtractor(cfg *config.Config) { s.extractor = extract.NewExtractor( s.logger, + s.cl, s.game.CreateContract, s.factoryContract.GetGamesAtOrAfter, cfg.IgnoredGames, @@ -158,10 +161,20 @@ func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config } func (s *Service) initL1Client(ctx context.Context, cfg *config.Config) error { - l1Client, err := dial.DialEthClientWithTimeout(ctx, dial.DefaultDialTimeout, s.logger, cfg.L1EthRpc) + l1RPC, err := dial.DialRPCClientWithTimeout(ctx, dial.DefaultDialTimeout, s.logger, cfg.L1EthRpc) if err != nil { return fmt.Errorf("failed to dial L1: %w", err) } + s.l1RPC = rpcclient.NewBaseRPCClient(l1RPC, rpcclient.WithCallTimeout(30*time.Second)) + s.l1Caller = batching.NewMultiCaller(s.l1RPC, batching.DefaultBatchSize) + // The RPC is trusted because the majority of data comes from contract calls which are not verified even when the + // RPC is untrusted and also avoids needing to update op-dispute-mon for L1 hard forks that change the header. + // Note that receipts are never fetched so the RPCKind has no actual effect. + clCfg := sources.L1ClientSimpleConfig(true, sources.RPCKindAny, 100) + l1Client, err := sources.NewL1Client(s.l1RPC, s.logger, s.metrics, clCfg) + if err != nil { + return fmt.Errorf("failed to init l1 client: %w", err) + } s.l1Client = l1Client return nil } @@ -202,38 +215,26 @@ func (s *Service) initMetricsServer(cfg *opmetrics.CLIConfig) error { } func (s *Service) initFactoryContract(cfg *config.Config) error { - factoryContract := contracts.NewDisputeGameFactoryContract(s.metrics, cfg.GameFactoryAddress, - batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize)) + factoryContract := contracts.NewDisputeGameFactoryContract(s.metrics, cfg.GameFactoryAddress, s.l1Caller) s.factoryContract = factoryContract return nil } func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) { - blockHashFetcher := func(ctx context.Context, blockNumber *big.Int) (common.Hash, error) { - block, err := s.l1Client.BlockByNumber(ctx, blockNumber) - if err != nil { - return common.Hash{}, fmt.Errorf("failed to fetch block by number: %w", err) - } - return block.Hash(), nil + headBlockFetcher := func(ctx context.Context) (eth.L1BlockRef, error) { + return s.l1Client.L1BlockRefByLabel(ctx, "latest") } l2ChallengesMonitor := NewL2ChallengesMonitor(s.logger, s.metrics) - s.monitor = newGameMonitor( - ctx, - s.logger, - s.cl, - s.metrics, - cfg.MonitorInterval, - cfg.GameWindow, + updateTimeMonitor := NewUpdateTimeMonitor(s.cl, s.metrics) + s.monitor = newGameMonitor(ctx, s.logger, s.cl, s.metrics, cfg.MonitorInterval, cfg.GameWindow, headBlockFetcher, + s.extractor.Extract, s.forecast.Forecast, s.bonds.CheckBonds, s.resolutions.CheckResolutions, s.claims.CheckClaims, s.withdrawals.CheckWithdrawals, l2ChallengesMonitor.CheckL2Challenges, - s.extractor.Extract, - s.l1Client.BlockNumber, - blockHashFetcher, - ) + updateTimeMonitor.CheckUpdateTimes) } func (s *Service) Start(ctx context.Context) error { diff --git a/op-dispute-mon/mon/types/types.go b/op-dispute-mon/mon/types/types.go index dff06dab90bf9..774ad3908cf7c 100644 --- a/op-dispute-mon/mon/types/types.go +++ b/op-dispute-mon/mon/types/types.go @@ -18,6 +18,7 @@ type EnrichedClaim struct { type EnrichedGameData struct { types.GameMetadata + LastUpdateTime time.Time L1Head common.Hash L1HeadNum uint64 L2BlockNumber uint64 diff --git a/op-dispute-mon/mon/update_times.go b/op-dispute-mon/mon/update_times.go new file mode 100644 index 0000000000000..3482775757b06 --- /dev/null +++ b/op-dispute-mon/mon/update_times.go @@ -0,0 +1,34 @@ +package mon + +import ( + "time" + + "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" +) + +type UpdateTimeMetrics interface { + RecordOldestGameUpdateTime(t time.Time) +} + +type UpdateTimeMonitor struct { + metrics UpdateTimeMetrics + clock clock.Clock +} + +func NewUpdateTimeMonitor(cl clock.Clock, metrics UpdateTimeMetrics) *UpdateTimeMonitor { + return &UpdateTimeMonitor{clock: cl, metrics: metrics} +} + +func (m *UpdateTimeMonitor) CheckUpdateTimes(games []*types.EnrichedGameData) { + // Report the current time if there are no games + // Otherwise the last update time would drop to 0 when there are no games, making it appear there were errors + earliest := m.clock.Now() + + for _, game := range games { + if game.LastUpdateTime.Before(earliest) { + earliest = game.LastUpdateTime + } + } + m.metrics.RecordOldestGameUpdateTime(earliest) +} diff --git a/op-dispute-mon/mon/update_times_test.go b/op-dispute-mon/mon/update_times_test.go new file mode 100644 index 0000000000000..a16da62b3c2e0 --- /dev/null +++ b/op-dispute-mon/mon/update_times_test.go @@ -0,0 +1,43 @@ +package mon + +import ( + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/stretchr/testify/require" +) + +func TestUpdateTimeMonitor_NoGames(t *testing.T) { + m := &mockUpdateTimeMetrics{} + cl := clock.NewDeterministicClock(time.UnixMilli(45892)) + monitor := NewUpdateTimeMonitor(cl, m) + monitor.CheckUpdateTimes(nil) + require.Equal(t, cl.Now(), m.oldestUpdateTime) + + cl.AdvanceTime(time.Minute) + monitor.CheckUpdateTimes([]*types.EnrichedGameData{}) + require.Equal(t, cl.Now(), m.oldestUpdateTime) +} + +func TestUpdateTimeMonitor_ReportsOldestUpdateTime(t *testing.T) { + m := &mockUpdateTimeMetrics{} + cl := clock.NewDeterministicClock(time.UnixMilli(45892)) + monitor := NewUpdateTimeMonitor(cl, m) + monitor.CheckUpdateTimes([]*types.EnrichedGameData{ + {LastUpdateTime: time.UnixMilli(4)}, + {LastUpdateTime: time.UnixMilli(3)}, + {LastUpdateTime: time.UnixMilli(7)}, + {LastUpdateTime: time.UnixMilli(9)}, + }) + require.Equal(t, time.UnixMilli(3), m.oldestUpdateTime) +} + +type mockUpdateTimeMetrics struct { + oldestUpdateTime time.Time +} + +func (m *mockUpdateTimeMetrics) RecordOldestGameUpdateTime(t time.Time) { + m.oldestUpdateTime = t +} diff --git a/op-dispute-mon/version/version.go b/op-dispute-mon/version/version.go index 834fc089b19ea..31ad6f3582afe 100644 --- a/op-dispute-mon/version/version.go +++ b/op-dispute-mon/version/version.go @@ -1,7 +1,7 @@ package version var ( - Version = "v0.1.0" + Version = "v0.0.0" Meta = "dev" ) diff --git a/op-e2e/Makefile b/op-e2e/Makefile index f3f67f8050da2..4c808ac45c04f 100644 --- a/op-e2e/Makefile +++ b/op-e2e/Makefile @@ -1,9 +1,11 @@ +num_cores := $(shell nproc) + # Generally, JUNIT_FILE is set in CI but may be specified to an arbitrary file location to emulate CI locally # If JUNIT_FILE is set, JSON_LOG_FILE should also be set ifdef JUNIT_FILE go_test = OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=false gotestsum --format=testname --junitfile=$(JUNIT_FILE) --jsonfile=$(JSON_LOG_FILE) -- -failfast # Note: -parallel must be set to match the number of cores in the resource class - go_test_flags = -timeout=60m -parallel=8 + go_test_flags = -timeout=60m -parallel=$(num_cores) else go_test = go test go_test_flags = -v @@ -36,10 +38,6 @@ test-fault-proofs: pre-test $(go_test) $(go_test_flags) ./faultproofs .PHONY: test-faultproofs -test-devnet: pre-test - $(go_test) $(go_test_flags) ./devnet -.PHONY: test-devnet - cannon-prestates: make -C .. cannon-prestate make -C .. cannon-prestate-mt @@ -71,7 +69,8 @@ clean: .PHONY: clean fuzz: - go test -run NOTAREALTEST -tags cgo_test -v -fuzztime 10s -fuzz FuzzFjordCostFunction ./opgeth - go test -run NOTAREALTEST -tags cgo_test -v -fuzztime 10s -fuzz FuzzFastLzGethSolidity ./opgeth - go test -run NOTAREALTEST -tags cgo_test -v -fuzztime 10s -fuzz FuzzFastLzCgo ./opgeth - + printf "%s\n" \ + "go test -run NOTAREALTEST -tags cgo_test -v -fuzztime 10s -fuzz FuzzFjordCostFunction ./opgeth" \ + "go test -run NOTAREALTEST -tags cgo_test -v -fuzztime 10s -fuzz FuzzFastLzGethSolidity ./opgeth" \ + "go test -run NOTAREALTEST -tags cgo_test -v -fuzztime 10s -fuzz FuzzFastLzCgo ./opgeth" \ + | parallel -j 8 {} diff --git a/op-e2e/README.md b/op-e2e/README.md index d6f0961942241..ba3855474615f 100644 --- a/op-e2e/README.md +++ b/op-e2e/README.md @@ -1,34 +1,105 @@ -# op-e2e +# `op-e2e` -The end to end tests in this repo depend on genesis state that is -created with the `bedrock-devnet` package. To create this state, -run the following commands from the root of the repository: +Issues: [monorepo](https://github.com/ethereum-optimism/optimism/issues?q=is%3Aissue%20state%3Aopen%20label%3AA-op-e2e) -```bash -make install-geth -make cannon-prestate -make devnet-allocs -``` +Pull requests: [monorepo](https://github.com/ethereum-optimism/optimism/pulls?q=is%3Aopen+is%3Apr+label%3AA-op-e2e) + +Design docs: +- [test infra draft design-doc]: active discussion of end-to-end testing approach -This will leave artifacts in the `.devnet` directory that will be -read into `op-e2e` at runtime. The default deploy configuration -used for starting all `op-e2e` based tests can be found in -`packages/contracts-bedrock/deploy-config/devnetL1.json`. There -are some values that are safe to change in memory in `op-e2e` at -runtime, but others cannot be changed or else it will result in -broken tests. Any changes to `devnetL1.json` should result in -rebuilding the `.devnet` artifacts before the new values will -be present in the `op-e2e` tests. +[test infra draft design-doc](https://github.com/ethereum-optimism/design-docs/pull/165) -## Running tests -Consult the [Makefile](./Makefile) in this directory. Run, e.g.: +`op-e2e` is a collection of Go integration tests. +It is named `e2e` after end-to-end testing, +for those tests where we integration-test the full system, rather than only specific services. + + +## Quickstart ```bash -make test-http +make test-actions +make test-ws ``` -### Troubleshooting -If you encounter errors: -* ensure you have the latest version of foundry installed: `just update-foundry` -* try deleting the `packages/contracts-bedrock/forge-artifacts` directory -* try `forge clean && rm -rf lib && forge install` within the `packages/contracts-bedrock` directory +## Overview + +`op-e2e` can be categorized as following: +- `op-e2e/actions/`: imperative test style, more DSL-like, with a focus on the state-transition parts of services. + Parallel processing is actively avoided, and a mock clock is used. + - `op-e2e/actions/*`: sub-packages categorize specific domains to test. + - `op-e2e/actions/interop`: notable sub-package, where multiple L2s are attached together, + for integration-testing across multiple L2 chains. + - `op-e2e/actions/proofs`: notable sub-package, where proof-related state-transition testing is implemented, + with experimental support to cover alternative proof implementations. +- `op-e2e/system`: integration tests with a L1 miner and a L2 with sequencer, verifier, batcher and proposer. + These tests do run each service almost fully, including parallel background jobs and real system clock. + These tests focus less on the onchain state-transition aspects, and more on the offchain integration aspects. + - `op-e2e/faultproofs`: system tests with fault-proofs stack attached + - `op-e2e/interop`: system tests with a distinct Interop "SuperSystem", to run multiple L2 chains. +- `op-e2e/opgeth`: integration tests between test-mocks and op-geth execution-engine. + - also includes upgrade-tests to ensure testing of op-stack Go components around a network upgrade. + +### `action`-tests + +Action tests are set up in a compositional way: +each service is instantiated as actor, and tests can choose to run just the relevant set of actors. +E.g. a test about data-availability can instantiate the batcher, but omit the proposer. + +One action, across all services, runs at a time. +No live background processing or system clock affects the actors: +this enables individual actions to be deterministic and reproducible. + +With this synchronous processing, action-test can reliably navigate towards +these otherwise hard-to-reach edge-cases, and ensure the state-transition of service, +and the interactions between this state, are covered. + +Action-tests do not cover background processes or peripherals. +E.g. P2P, CLI usage, and dynamic block building are not covered. + +### `system`-tests + +System tests are more complete than `action` tests, but also require a live system. +This trade-off enables coverage of most of each Go service, +at the cost of making navigation to cover the known edge-cases less reliable and reproducible. +This test-type is thus used primarily for testing of the offchain service aspects. + +By running a more full system, test-runners also run into resource-limits more quickly. +This may result in lag or even stalled services. +Improvements, as described in the [test infra draft design-doc], +are in active development, to make test execution more reliable. + +### `op-e2e/opgeth` + +Integration-testing with op-geth, to cover engine behavior, without setting up a full test environment. +These tests are limited in scope, and may be changed at a later stage, to support alternative EL implementations. + +## Product + +### Optimization target + +Historically `op-e2e` has been optimized for test-coverage of the Go OP-Stack. +This is changing with the advance of alternative OP-Stack client implementations. + +New test framework improvements should optimize for multi-client testing. + +### Vision + +Generally, design-discussion and feedback from active test users converges on: +- a need to share test-resources, to host more tests while reducing overhead. +- a need for a DSL, to better express common test constructs. +- less involved test pre-requisites: the environment should be light and simple, welcoming new contributors. + E.g. no undocumented one-off makefile prerequisites. + +## Design principles + +- Interfaces first. We should not hardcode test-utilities against any specific client implementation, + this makes a test less parameterizable and less cross-client portable. +- Abstract setup to make it the default to reduce resource usage. + E.g. RPC transports can run in-process, and avoid websocket or HTTP costs, + and ideally the test-writer does not have to think about the difference. +- Avoid one-off test chain-configurations. Tests with more realistic parameters are more comparable to production, + and easier consolidated onto shared testing resources. +- Write helpers and DSL utilities, avoid re-implementing common testing steps. + The better the test environment, the more inviting it is for someone new to help improve test coverage. +- Use the right test-type. Do not spawn a full system for something of very limited scope, + e.g. when it fits better in a unit-test. diff --git a/op-e2e/actions/altda/altda_test.go b/op-e2e/actions/altda/altda_test.go index 21dcbf6b9038e..3c7e06a380fcf 100644 --- a/op-e2e/actions/altda/altda_test.go +++ b/op-e2e/actions/altda/altda_test.go @@ -72,7 +72,7 @@ func NewL2AltDA(t helpers.Testing, params ...AltDAParam) *L2AltDA { l1Client := miner.EthClient() jwtPath := e2eutils.WriteDefaultJWT(t) - engine := helpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath) + engine := helpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath) engCl := engine.EngineClient(t, sd.RollupCfg) storage := &altda.DAErrFaker{Client: altda.NewMockDAClient(log)} @@ -136,7 +136,7 @@ func (a *L2AltDA) StorageClient() *altda.DAErrFaker { func (a *L2AltDA) NewVerifier(t helpers.Testing) *helpers.L2Verifier { jwtPath := e2eutils.WriteDefaultJWT(t) - engine := helpers.NewL2Engine(t, a.log, a.sd.L2Cfg, a.sd.RollupCfg.Genesis.L1, jwtPath) + engine := helpers.NewL2Engine(t, a.log, a.sd.L2Cfg, jwtPath) engCl := engine.EngineClient(t, a.sd.RollupCfg) l1F, err := sources.NewL1Client(a.miner.RPCClient(), a.log, nil, sources.L1ClientDefaultConfig(a.sd.RollupCfg, false, sources.RPCKindBasic)) require.NoError(t, err) diff --git a/op-e2e/actions/batcher/l2_batcher_test.go b/op-e2e/actions/batcher/l2_batcher_test.go index 8906dcbed4ea5..3fec73db4f7c0 100644 --- a/op-e2e/actions/batcher/l2_batcher_test.go +++ b/op-e2e/actions/batcher/l2_batcher_test.go @@ -408,7 +408,7 @@ func ExtendedTimeWithoutL1Batches(gt *testing.T, deltaTimeOffset *hexutil.Uint64 // - Fill 40 L2 blocks to near max-capacity, with txs of 120 KB each // - Buffer the L2 blocks into channels together as much as possible, submit data-txs only when necessary // (just before crossing the max RLP channel size) -// - Limit the data-tx size to 40 KB, to force data to be split across multiple datat-txs +// - Limit the data-tx size to 40 KB, to force data to be split across multiple data-txs // - Defer all data-tx inclusion till the end // - Fill L1 blocks with data-txs until we have processed them all // - Run the verifier, and check if it derives the same L2 chain as was created by the sequencer. diff --git a/op-e2e/actions/derivation/blocktime_test.go b/op-e2e/actions/derivation/blocktime_test.go index 1855013aad6da..bd5594864af55 100644 --- a/op-e2e/actions/derivation/blocktime_test.go +++ b/op-e2e/actions/derivation/blocktime_test.go @@ -7,6 +7,7 @@ import ( actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" upgradesHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/upgrades/helpers" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -163,11 +164,13 @@ func LargeL1Gaps(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { dp.DeployConfig.L2BlockTime = 2 dp.DeployConfig.SequencerWindowSize = 4 dp.DeployConfig.MaxSequencerDrift = 32 - dp.DeployConfig.L2GenesisEcotoneTimeOffset = nil - dp.DeployConfig.L2GenesisFjordTimeOffset = nil + if deltaTimeOffset != nil { + dp.DeployConfig.ActivateForkAtOffset(rollup.Delta, uint64(*deltaTimeOffset)) + } else { + dp.DeployConfig.ActivateForkAtGenesis(rollup.Canyon) + } // TODO(client-pod#831): The Ecotone (and Fjord) activation blocks don't include user txs, // so disabling these forks for now. - upgradesHelpers.ApplyDeltaTimeOffset(dp, deltaTimeOffset) sd := e2eutils.Setup(t, dp, actionsHelpers.DefaultAlloc) log := testlog.Logger(t, log.LevelDebug) diff --git a/op-e2e/actions/derivation/reorg_test.go b/op-e2e/actions/derivation/reorg_test.go index 10155a471a6f2..09135151d3b20 100644 --- a/op-e2e/actions/derivation/reorg_test.go +++ b/op-e2e/actions/derivation/reorg_test.go @@ -592,7 +592,7 @@ func RestartOpGeth(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { l1F, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard)) require.NoError(t, err) // Sequencer - seqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, dbOption) + seqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath, dbOption) engRpc := &rpcWrapper{seqEng.RPCClient()} l2Cl, err := sources.NewEngineClient(engRpc, log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg)) require.NoError(t, err) @@ -647,7 +647,7 @@ func RestartOpGeth(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { // close the sequencer engine require.NoError(t, seqEng.Close()) // and start a new one with same db path - seqEngNew := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, dbOption) + seqEngNew := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath, dbOption) // swap in the new rpc. This is as close as we can get to reconnecting to a new in-memory rpc connection engRpc.RPC = seqEngNew.RPCClient() @@ -679,7 +679,7 @@ func ConflictingL2Blocks(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { // Extra setup: a full alternative sequencer, sequencer engine, and batcher jwtPath := e2eutils.WriteDefaultJWT(t) - altSeqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath) + altSeqEng := actionsHelpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath) altSeqEngCl, err := sources.NewEngineClient(altSeqEng.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg)) require.NoError(t, err) l1F, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard)) diff --git a/op-e2e/actions/derivation/system_config_test.go b/op-e2e/actions/derivation/system_config_test.go index 362c9f2dc8547..ecfe466cadb0f 100644 --- a/op-e2e/actions/derivation/system_config_test.go +++ b/op-e2e/actions/derivation/system_config_test.go @@ -237,6 +237,7 @@ func GPOParamsChange(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { dp.DeployConfig.L2GenesisEcotoneTimeOffset = nil dp.DeployConfig.L2GenesisFjordTimeOffset = nil dp.DeployConfig.L2GenesisGraniteTimeOffset = nil + dp.DeployConfig.L2GenesisHoloceneTimeOffset = nil sd := e2eutils.Setup(t, dp, actionsHelpers.DefaultAlloc) log := testlog.Logger(t, log.LevelDebug) diff --git a/op-e2e/actions/helpers/env.go b/op-e2e/actions/helpers/env.go new file mode 100644 index 0000000000000..77911aa80bf5c --- /dev/null +++ b/op-e2e/actions/helpers/env.go @@ -0,0 +1,118 @@ +package helpers + +import ( + "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + e2ecfg "github.com/ethereum-optimism/optimism/op-e2e/config" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/sync" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +type Env struct { + Log log.Logger + Logs *testlog.CapturingHandler + + DeployParams *e2eutils.DeployParams + SetupData *e2eutils.SetupData + + Miner *L1Miner + Seq *L2Sequencer + SeqEngine *L2Engine + Verifier *L2Verifier + VerifEngine *L2Engine + Batcher *L2Batcher + Alice *CrossLayerUser + + AddressCorpora []common.Address +} + +type EnvOpt struct { + DeployConfigMod func(*genesis.DeployConfig) +} + +func WithActiveFork(fork rollup.ForkName, offset uint64) EnvOpt { + return EnvOpt{ + DeployConfigMod: func(d *genesis.DeployConfig) { + d.ActivateForkAtOffset(fork, offset) + }, + } +} + +func WithActiveGenesisFork(fork rollup.ForkName) EnvOpt { + return WithActiveFork(fork, 0) +} + +// DefaultFork specifies the default fork to use when setting up the action test environment. +// Currently manually set to Holocene. +// Replace with `var DefaultFork = func() rollup.ForkName { return rollup.AllForks[len(rollup.AllForks)-1] }()` after Interop launch. +const DefaultFork = rollup.Holocene + +// SetupEnv sets up a default action test environment. If no fork is specified, the default fork as +// specified by the package variable [defaultFork] is used. +func SetupEnv(t Testing, opts ...EnvOpt) (env Env) { + dp := e2eutils.MakeDeployParams(t, DefaultRollupTestParams()) + env.DeployParams = dp + + log, logs := testlog.CaptureLogger(t, log.LevelDebug) + env.Log, env.Logs = log, logs + + dp.DeployConfig.ActivateForkAtGenesis(DefaultFork) + for _, opt := range opts { + if dcMod := opt.DeployConfigMod; dcMod != nil { + dcMod(dp.DeployConfig) + } + } + + sd := e2eutils.Setup(t, dp, DefaultAlloc) + env.SetupData = sd + env.AddressCorpora = e2eutils.CollectAddresses(sd, dp) + + env.Miner, env.SeqEngine, env.Seq = SetupSequencerTest(t, sd, log) + env.Miner.ActL1SetFeeRecipient(common.Address{'A'}) + env.VerifEngine, env.Verifier = SetupVerifier(t, sd, log, env.Miner.L1Client(t, sd.RollupCfg), env.Miner.BlobStore(), &sync.Config{}) + rollupSeqCl := env.Seq.RollupClient() + env.Batcher = NewL2Batcher(log, sd.RollupCfg, DefaultBatcherCfg(dp), + rollupSeqCl, env.Miner.EthClient(), env.SeqEngine.EthClient(), env.SeqEngine.EngineClient(t, sd.RollupCfg)) + + alice := NewCrossLayerUser(log, dp.Secrets.Alice, rand.New(rand.NewSource(0xa57b)), e2ecfg.AllocTypeStandard) + alice.L1.SetUserEnv(env.L1UserEnv(t)) + alice.L2.SetUserEnv(env.L2UserEnv(t)) + env.Alice = alice + + return +} + +func (env Env) L1UserEnv(t Testing) *BasicUserEnv[*L1Bindings] { + l1EthCl := env.Miner.EthClient() + return &BasicUserEnv[*L1Bindings]{ + EthCl: l1EthCl, + Signer: types.LatestSigner(env.SetupData.L1Cfg.Config), + AddressCorpora: env.AddressCorpora, + Bindings: NewL1Bindings(t, l1EthCl, e2ecfg.AllocTypeStandard), + } +} + +func (env Env) L2UserEnv(t Testing) *BasicUserEnv[*L2Bindings] { + l2EthCl := env.SeqEngine.EthClient() + return &BasicUserEnv[*L2Bindings]{ + EthCl: l2EthCl, + Signer: types.LatestSigner(env.SetupData.L2Cfg.Config), + AddressCorpora: env.AddressCorpora, + Bindings: NewL2Bindings(t, l2EthCl, env.SeqEngine.GethClient()), + } +} + +func (env Env) ActBatchSubmitAllAndMine(t Testing) (l1InclusionBlock *types.Block) { + env.Batcher.ActSubmitAll(t) + batchTx := env.Batcher.LastSubmitted + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTxByHash(batchTx.Hash())(t) + return env.Miner.ActL1EndBlock(t) +} diff --git a/op-e2e/actions/helpers/l1_miner.go b/op-e2e/actions/helpers/l1_miner.go index bf1bb415fe4ff..108c11d3e1667 100644 --- a/op-e2e/actions/helpers/l1_miner.go +++ b/op-e2e/actions/helpers/l1_miner.go @@ -203,10 +203,10 @@ func (s *L1Miner) ActL1SetFeeRecipient(coinbase common.Address) { } // ActL1EndBlock finishes the new L1 block, and applies it to the chain as unsafe block -func (s *L1Miner) ActL1EndBlock(t Testing) { +func (s *L1Miner) ActL1EndBlock(t Testing) *types.Block { if !s.l1Building { t.InvalidAction("cannot end L1 block when not building block") - return + return nil } s.l1Building = false @@ -253,11 +253,12 @@ func (s *L1Miner) ActL1EndBlock(t Testing) { if err != nil { t.Fatalf("failed to insert block into l1 chain") } + return block } -func (s *L1Miner) ActEmptyBlock(t Testing) { +func (s *L1Miner) ActEmptyBlock(t Testing) *types.Block { s.ActL1StartBlock(12)(t) - s.ActL1EndBlock(t) + return s.ActL1EndBlock(t) } func (s *L1Miner) Close() error { diff --git a/op-e2e/actions/helpers/l2_batcher.go b/op-e2e/actions/helpers/l2_batcher.go index a106c56876635..9fc9971a26e26 100644 --- a/op-e2e/actions/helpers/l2_batcher.go +++ b/op-e2e/actions/helpers/l2_batcher.go @@ -146,11 +146,52 @@ func (s *L2Batcher) Reset() { // ActL2BatchBuffer adds the next L2 block to the batch buffer. // If the buffer is being submitted, the buffer is wiped. -func (s *L2Batcher) ActL2BatchBuffer(t Testing) { - require.NoError(t, s.Buffer(t), "failed to add block to channel") +func (s *L2Batcher) ActL2BatchBuffer(t Testing, opts ...BlockModifier) { + require.NoError(t, s.Buffer(t, opts...), "failed to add block to channel") } -type BlockModifier = func(block *types.Block) +// ActCreateChannel creates a channel if we don't have one yet. +func (s *L2Batcher) ActCreateChannel(t Testing, useSpanChannelOut bool) { + var err error + if s.L2ChannelOut == nil { + var ch ChannelOutIface + if s.l2BatcherCfg.GarbageCfg != nil { + ch, err = NewGarbageChannelOut(s.l2BatcherCfg.GarbageCfg) + } else { + target := batcher.MaxDataSize(1, s.l2BatcherCfg.MaxL1TxSize) + c, e := compressor.NewShadowCompressor(compressor.Config{ + TargetOutputSize: target, + CompressionAlgo: derive.Zlib, + }) + require.NoError(t, e, "failed to create compressor") + + if s.l2BatcherCfg.ForceSubmitSingularBatch && s.l2BatcherCfg.ForceSubmitSpanBatch { + t.Fatalf("ForceSubmitSingularBatch and ForceSubmitSpanBatch cannot be set to true at the same time") + } else { + chainSpec := rollup.NewChainSpec(s.rollupCfg) + // use span batch if we're forcing it or if we're at/beyond delta + if s.l2BatcherCfg.ForceSubmitSpanBatch || useSpanChannelOut { + ch, err = derive.NewSpanChannelOut(target, derive.Zlib, chainSpec) + // use singular batches in all other cases + } else { + ch, err = derive.NewSingularChannelOut(c, chainSpec) + } + } + } + require.NoError(t, err, "failed to create channel") + s.L2ChannelOut = ch + } +} + +type BlockModifier = func(block *types.Block) *types.Block + +func BlockLogger(t e2eutils.TestingBase) BlockModifier { + f := func(block *types.Block) *types.Block { + t.Log("added block", "num", block.Number(), "txs", block.Transactions(), "time", block.Time()) + return block + } + return f +} func (s *L2Batcher) Buffer(t Testing, opts ...BlockModifier) error { if s.l2Submitting { // break ongoing submitting work if necessary @@ -197,38 +238,13 @@ func (s *L2Batcher) Buffer(t Testing, opts ...BlockModifier) error { // Apply modifications to the block for _, f := range opts { - f(block) + if f != nil { + block = f(block) + } } - // Create channel if we don't have one yet - if s.L2ChannelOut == nil { - var ch ChannelOutIface - if s.l2BatcherCfg.GarbageCfg != nil { - ch, err = NewGarbageChannelOut(s.l2BatcherCfg.GarbageCfg) - } else { - target := batcher.MaxDataSize(1, s.l2BatcherCfg.MaxL1TxSize) - c, e := compressor.NewShadowCompressor(compressor.Config{ - TargetOutputSize: target, - CompressionAlgo: derive.Zlib, - }) - require.NoError(t, e, "failed to create compressor") + s.ActCreateChannel(t, s.rollupCfg.IsDelta(block.Time())) - if s.l2BatcherCfg.ForceSubmitSingularBatch && s.l2BatcherCfg.ForceSubmitSpanBatch { - t.Fatalf("ForceSubmitSingularBatch and ForceSubmitSpanBatch cannot be set to true at the same time") - } else { - chainSpec := rollup.NewChainSpec(s.rollupCfg) - // use span batch if we're forcing it or if we're at/beyond delta - if s.l2BatcherCfg.ForceSubmitSpanBatch || s.rollupCfg.IsDelta(block.Time()) { - ch, err = derive.NewSpanChannelOut(target, derive.Zlib, chainSpec) - // use singular batches in all other cases - } else { - ch, err = derive.NewSingularChannelOut(c, chainSpec) - } - } - } - require.NoError(t, err, "failed to create channel") - s.L2ChannelOut = ch - } if _, err := s.L2ChannelOut.AddBlock(s.rollupCfg, block); err != nil { return err } @@ -238,6 +254,30 @@ func (s *L2Batcher) Buffer(t Testing, opts ...BlockModifier) error { return nil } +// ActAddBlockByNumber causes the batcher to pull the block with the provided +// number, and add it to its ChannelOut. +func (s *L2Batcher) ActAddBlockByNumber(t Testing, blockNumber int64, opts ...BlockModifier) { + block, err := s.l2.BlockByNumber(t.Ctx(), big.NewInt(blockNumber)) + require.NoError(t, err) + require.NotNil(t, block) + + // cache block hash before we modify the block + blockHash := block.Hash() + + // Apply modifications to the block + for _, f := range opts { + if f != nil { + block = f(block) + } + } + + _, err = s.L2ChannelOut.AddBlock(s.rollupCfg, block) + require.NoError(t, err) + ref, err := s.engCl.L2BlockRefByHash(t.Ctx(), blockHash) + require.NoError(t, err, "failed to get L2BlockRef") + s.L2BufferedBlock = ref +} + func (s *L2Batcher) ActL2ChannelClose(t Testing) { // Don't run this action if there's no data to submit if s.L2ChannelOut == nil { diff --git a/op-e2e/actions/helpers/l2_engine.go b/op-e2e/actions/helpers/l2_engine.go index 5839a20798d31..8d2f318a08dc9 100644 --- a/op-e2e/actions/helpers/l2_engine.go +++ b/op-e2e/actions/helpers/l2_engine.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/testutils" ) @@ -37,8 +36,6 @@ type L2Engine struct { node *node.Node Eth *geth.Ethereum - rollupGenesis *rollup.Genesis - // L2 evm / chain l2Chain *core.BlockChain l2Signer types.Signer @@ -50,20 +47,14 @@ type L2Engine struct { type EngineOption func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error -func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, rollupGenesisL1 eth.BlockID, jwtPath string, options ...EngineOption) *L2Engine { +func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, jwtPath string, options ...EngineOption) *L2Engine { n, ethBackend, apiBackend := newBackend(t, genesis, jwtPath, options) engineApi := engineapi.NewL2EngineAPI(log, apiBackend, ethBackend.Downloader()) chain := ethBackend.BlockChain() - genesisBlock := chain.Genesis() eng := &L2Engine{ - log: log, - node: n, - Eth: ethBackend, - rollupGenesis: &rollup.Genesis{ - L1: rollupGenesisL1, - L2: eth.BlockID{Hash: genesisBlock.Hash(), Number: genesisBlock.NumberU64()}, - L2Time: genesis.Timestamp, - }, + log: log, + node: n, + Eth: ethBackend, l2Chain: chain, l2Signer: types.LatestSigner(genesis.Config), EngineApi: engineApi, @@ -200,9 +191,30 @@ func (e *L2Engine) ActL2RPCFail(t Testing, err error) { } } +// ActL2IncludeTx includes the next transaction from the given address in the block that is being built, +// skipping the usual check for e.EngineApi.ForcedEmpty() +func (e *L2Engine) ActL2IncludeTxIgnoreForcedEmpty(from common.Address) Action { + return func(t Testing) { + if e.EngineApi.ForcedEmpty() { + e.log.Info("Ignoring e.L2ForceEmpty=true") + } + + tx := firstValidTx(t, from, e.EngineApi.PendingIndices, e.Eth.TxPool().ContentFrom, e.EthClient().NonceAt) + err := e.EngineApi.IncludeTx(tx, from) + if errors.Is(err, engineapi.ErrNotBuildingBlock) { + t.InvalidAction(err.Error()) + } else if errors.Is(err, engineapi.ErrUsesTooMuchGas) { + t.InvalidAction("included tx uses too much gas: %v", err) + } else if err != nil { + require.NoError(t, err, "include tx") + } + } +} + // ActL2IncludeTx includes the next transaction from the given address in the block that is being built func (e *L2Engine) ActL2IncludeTx(from common.Address) Action { return func(t Testing) { + if e.EngineApi.ForcedEmpty() { e.log.Info("Skipping including a transaction because e.L2ForceEmpty is true") return diff --git a/op-e2e/actions/helpers/l2_engine_test.go b/op-e2e/actions/helpers/l2_engine_test.go index b859b393b6217..115921bea94bd 100644 --- a/op-e2e/actions/helpers/l2_engine_test.go +++ b/op-e2e/actions/helpers/l2_engine_test.go @@ -40,7 +40,7 @@ func TestL2EngineAPI(gt *testing.T) { tdb := triedb.NewDatabase(db, &triedb.Config{HashDB: hashdb.Defaults}) sd.L2Cfg.MustCommit(db, tdb) - engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath) + engine := NewL2Engine(t, log, sd.L2Cfg, jwtPath) l2Cl, err := sources.NewEngineClient(engine.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg)) require.NoError(t, err) @@ -115,7 +115,7 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) { tdb := triedb.NewDatabase(db, &triedb.Config{HashDB: hashdb.Defaults}) sd.L2Cfg.MustCommit(db, tdb) - engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath) + engine := NewL2Engine(t, log, sd.L2Cfg, jwtPath) t.Cleanup(func() { _ = engine.Close() }) @@ -211,7 +211,7 @@ func TestL2EngineAPIFail(gt *testing.T) { dp := e2eutils.MakeDeployParams(t, DefaultRollupTestParams()) sd := e2eutils.Setup(t, dp, DefaultAlloc) log := testlog.Logger(t, log.LevelDebug) - engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath) + engine := NewL2Engine(t, log, sd.L2Cfg, jwtPath) // mock an RPC failure mockErr := errors.New("mock L2 RPC error") engine.ActL2RPCFail(t, mockErr) diff --git a/op-e2e/actions/helpers/l2_sequencer.go b/op-e2e/actions/helpers/l2_sequencer.go index 424e12b23fda0..1b8699db856db 100644 --- a/op-e2e/actions/helpers/l2_sequencer.go +++ b/op-e2e/actions/helpers/l2_sequencer.go @@ -43,7 +43,8 @@ func (m *MockL1OriginSelector) FindL1Origin(ctx context.Context, l2Head eth.L2Bl type L2Sequencer struct { *L2Verifier - sequencer *sequencing.Sequencer + sequencer *sequencing.Sequencer + attrBuilder *derive.FetchingAttributesBuilder failL2GossipUnsafeBlock error // mock error @@ -52,7 +53,8 @@ type L2Sequencer struct { func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, blobSrc derive.L1BlobsFetcher, altDASrc driver.AltDAIface, eng L2API, cfg *rollup.Config, seqConfDepth uint64, - interopBackend interop.InteropBackend) *L2Sequencer { + interopBackend interop.InteropBackend, +) *L2Sequencer { ver := NewL2Verifier(t, log, l1, blobSrc, altDASrc, eng, cfg, &sync.Config{}, safedb.Disabled, interopBackend) attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, eng) seqConfDepthL1 := confdepth.NewConfDepth(seqConfDepth, ver.syncStatus.L1Head, l1) @@ -65,7 +67,7 @@ func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, blobSrc deri conduc := &conductor.NoOpConductor{} asyncGossip := async.NoOpGossiper{} seq := sequencing.NewSequencer(t.Ctx(), log, cfg, attrBuilder, l1OriginSelector, - seqStateListener, conduc, asyncGossip, metr) + seqStateListener, conduc, asyncGossip, metr, nil) opts := event.DefaultRegisterOpts() opts.Emitter = event.EmitterOpts{ Limiting: true, @@ -84,6 +86,7 @@ func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, blobSrc deri return &L2Sequencer{ L2Verifier: ver, sequencer: seq, + attrBuilder: attrBuilder, mockL1OriginSelector: l1OriginSelector, failL2GossipUnsafeBlock: nil, } @@ -130,21 +133,36 @@ func (s *L2Sequencer) ActL2EndBlock(t Testing) { "sync status must be accurate after block building") } +func (s *L2Sequencer) ActL2EmptyBlock(t Testing) { + s.ActL2StartBlock(t) + s.ActL2EndBlock(t) +} + // ActL2KeepL1Origin makes the sequencer use the current L1 origin, even if the next origin is available. func (s *L2Sequencer) ActL2KeepL1Origin(t Testing) { parent := s.engine.UnsafeL2Head() - // force old origin, for testing purposes + // force old origin oldOrigin, err := s.l1.L1BlockRefByHash(t.Ctx(), parent.L1Origin.Hash) require.NoError(t, err, "failed to get current origin: %s", parent.L1Origin) s.mockL1OriginSelector.originOverride = oldOrigin } +// ActL2ForceAdvanceL1Origin forces the sequencer to advance the current L1 origin, even if the next origin's timestamp is too new. +func (s *L2Sequencer) ActL2ForceAdvanceL1Origin(t Testing) { + s.attrBuilder.TestSkipL1OriginCheck() // skip check in attributes builder + parent := s.engine.UnsafeL2Head() + // force next origin + nextNum := parent.L1Origin.Number + 1 + nextOrigin, err := s.l1.L1BlockRefByNumber(t.Ctx(), nextNum) + require.NoError(t, err, "failed to get next origin by number: %d", nextNum) + s.mockL1OriginSelector.originOverride = nextOrigin +} + // ActBuildToL1Head builds empty blocks until (incl.) the L1 head becomes the L2 origin func (s *L2Sequencer) ActBuildToL1Head(t Testing) { for s.engine.UnsafeL2Head().L1Origin.Number < s.syncStatus.L1Head().Number { s.ActL2PipelineFull(t) - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) } } @@ -152,8 +170,7 @@ func (s *L2Sequencer) ActBuildToL1Head(t Testing) { func (s *L2Sequencer) ActBuildToL1HeadUnsafe(t Testing) { for s.engine.UnsafeL2Head().L1Origin.Number < s.syncStatus.L1Head().Number { // Note: the derivation pipeline does not run, we are just sequencing a block on top of the existing L2 chain. - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) } } @@ -166,8 +183,7 @@ func (s *L2Sequencer) ActBuildToL1HeadExcl(t Testing) { if nextOrigin.Number >= s.syncStatus.L1Head().Number { break } - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) } } @@ -180,36 +196,40 @@ func (s *L2Sequencer) ActBuildToL1HeadExclUnsafe(t Testing) { if nextOrigin.Number >= s.syncStatus.L1Head().Number { break } - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) } } func (s *L2Sequencer) ActBuildL2ToTime(t Testing, target uint64) { for s.L2Unsafe().Time < target { - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) } } func (s *L2Sequencer) ActBuildL2ToEcotone(t Testing) { require.NotNil(t, s.RollupCfg.EcotoneTime, "cannot activate Ecotone when it is not scheduled") for s.L2Unsafe().Time < *s.RollupCfg.EcotoneTime { - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) } } + func (s *L2Sequencer) ActBuildL2ToFjord(t Testing) { require.NotNil(t, s.RollupCfg.FjordTime, "cannot activate FjordTime when it is not scheduled") for s.L2Unsafe().Time < *s.RollupCfg.FjordTime { - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) } } + func (s *L2Sequencer) ActBuildL2ToGranite(t Testing) { require.NotNil(t, s.RollupCfg.GraniteTime, "cannot activate GraniteTime when it is not scheduled") for s.L2Unsafe().Time < *s.RollupCfg.GraniteTime { - s.ActL2StartBlock(t) - s.ActL2EndBlock(t) + s.ActL2EmptyBlock(t) + } +} + +func (s *L2Sequencer) ActBuildL2ToHolocene(t Testing) { + require.NotNil(t, s.RollupCfg.HoloceneTime, "cannot activate HoloceneTime when it is not scheduled") + for s.L2Unsafe().Time < *s.RollupCfg.HoloceneTime { + s.ActL2EmptyBlock(t) } } diff --git a/op-e2e/actions/helpers/setups.go b/op-e2e/actions/helpers/setups.go index 26e19eae82a05..3322dea4223e0 100644 --- a/op-e2e/actions/helpers/setups.go +++ b/op-e2e/actions/helpers/setups.go @@ -25,7 +25,7 @@ func SetupSequencerTest(t Testing, sd *e2eutils.SetupData, log log.Logger, opts l1F, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard)) require.NoError(t, err) - engine := NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, EngineWithP2P()) + engine := NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, jwtPath, EngineWithP2P()) l2Cl, err := sources.NewEngineClient(engine.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg)) require.NoError(t, err) @@ -40,7 +40,7 @@ func SetupVerifier(t Testing, sd *e2eutils.SetupData, log log.Logger, opt(cfg) } jwtPath := e2eutils.WriteDefaultJWT(t) - engine := NewL2Engine(t, log.New("role", "verifier-engine"), sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, EngineWithP2P()) + engine := NewL2Engine(t, log.New("role", "verifier-engine"), sd.L2Cfg, jwtPath, EngineWithP2P()) engCl := engine.EngineClient(t, sd.RollupCfg) verifier := NewL2Verifier(t, log.New("role", "verifier"), l1F, blobSrc, altda.Disabled, engCl, sd.RollupCfg, syncCfg, cfg.SafeHeadListener, cfg.InteropBackend) return engine, verifier diff --git a/op-e2e/actions/helpers/user_test.go b/op-e2e/actions/helpers/user_test.go index 8990ed5fdd96d..6ac7e598c5936 100644 --- a/op-e2e/actions/helpers/user_test.go +++ b/op-e2e/actions/helpers/user_test.go @@ -25,6 +25,8 @@ type hardforkScheduledTest struct { deltaTime *hexutil.Uint64 ecotoneTime *hexutil.Uint64 fjordTime *hexutil.Uint64 + graniteTime *hexutil.Uint64 + holoceneTime *hexutil.Uint64 runToFork string allocType config.AllocType } @@ -39,6 +41,10 @@ func (tc *hardforkScheduledTest) GetFork(fork string) *uint64 { func (tc *hardforkScheduledTest) fork(fork string) **hexutil.Uint64 { switch fork { + case "holocene": + return &tc.holoceneTime + case "granite": + return &tc.graniteTime case "fjord": return &tc.fjordTime case "ecotone": @@ -80,6 +86,8 @@ func testCrossLayerUser(t *testing.T, allocType config.AllocType) { "delta", "ecotone", "fjord", + "granite", + "holocene", } for i, fork := range forks { i := i @@ -136,6 +144,8 @@ func runCrossLayerUserTest(gt *testing.T, test hardforkScheduledTest) { dp.DeployConfig.L2GenesisDeltaTimeOffset = test.deltaTime dp.DeployConfig.L2GenesisEcotoneTimeOffset = test.ecotoneTime dp.DeployConfig.L2GenesisFjordTimeOffset = test.fjordTime + dp.DeployConfig.L2GenesisGraniteTimeOffset = test.graniteTime + dp.DeployConfig.L2GenesisHoloceneTimeOffset = test.holoceneTime if test.canyonTime != nil { require.Zero(t, uint64(*test.canyonTime)%uint64(dp.DeployConfig.L2BlockTime), "canyon fork must be aligned") diff --git a/op-e2e/actions/interop/interop.go b/op-e2e/actions/interop/interop.go new file mode 100644 index 0000000000000..4ac2b0cb80ffc --- /dev/null +++ b/op-e2e/actions/interop/interop.go @@ -0,0 +1,236 @@ +package interop + +import ( + "context" + "os" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + + altda "github.com/ethereum-optimism/optimism/op-alt-da" + batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/interopgen" + "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/interop" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/ethereum-optimism/optimism/op-supervisor/metrics" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +const ( + foundryArtifactsDir = "../../../packages/contracts-bedrock/forge-artifacts" + sourceMapDir = "../../../packages/contracts-bedrock" +) + +// Chain holds the most common per-chain action-test data and actors +type Chain struct { + ChainID types.ChainID + + RollupCfg *rollup.Config + ChainCfg *params.ChainConfig + BatcherAddr common.Address + + Sequencer *helpers.L2Sequencer + SequencerEngine *helpers.L2Engine + Batcher *helpers.L2Batcher +} + +// InteropSetup holds the chain deployment and config contents, before instantiating any services. +type InteropSetup struct { + Log log.Logger + Deployment *interopgen.WorldDeployment + Out *interopgen.WorldOutput + DepSet *depset.StaticConfigDependencySet + Keys devkeys.Keys + T helpers.Testing +} + +// InteropActors holds a bundle of global actors and actors of 2 chains. +type InteropActors struct { + L1Miner *helpers.L1Miner + Supervisor *SupervisorActor + ChainA *Chain + ChainB *Chain +} + +// SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains. +func SetupInterop(t helpers.Testing) *InteropSetup { + logger := testlog.Logger(t, log.LevelDebug) + + recipe := interopgen.InteropDevRecipe{ + L1ChainID: 900100, + L2ChainIDs: []uint64{900200, 900201}, + GenesisTimestamp: uint64(time.Now().Unix() + 3), + } + hdWallet, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + worldCfg, err := recipe.Build(hdWallet) + require.NoError(t, err) + + // create the foundry artifacts and source map + foundryArtifacts := foundry.OpenArtifactsDir(foundryArtifactsDir) + sourceMap := foundry.NewSourceMapFS(os.DirFS(sourceMapDir)) + + // deploy the world, using the logger, foundry artifacts, source map, and world configuration + worldDeployment, worldOutput, err := interopgen.Deploy(logger, foundryArtifacts, sourceMap, worldCfg) + require.NoError(t, err) + + return &InteropSetup{ + Log: logger, + Deployment: worldDeployment, + Out: worldOutput, + DepSet: worldToDepSet(t, worldOutput), + Keys: hdWallet, + T: t, + } +} + +func (is *InteropSetup) CreateActors() *InteropActors { + l1Miner := helpers.NewL1Miner(is.T, is.Log.New("role", "l1Miner"), is.Out.L1.Genesis) + supervisorAPI := NewSupervisor(is.T, is.Log, is.DepSet) + require.NoError(is.T, supervisorAPI.Start(is.T.Ctx())) + is.T.Cleanup(func() { + require.NoError(is.T, supervisorAPI.Stop(context.Background())) + }) + chainA := createL2Services(is.T, is.Log, l1Miner, is.Keys, is.Out.L2s["900200"], supervisorAPI) + chainB := createL2Services(is.T, is.Log, l1Miner, is.Keys, is.Out.L2s["900201"], supervisorAPI) + // Hook up L2 RPCs to supervisor, to fetch event data from + require.NoError(is.T, supervisorAPI.AddL2RPC(is.T.Ctx(), chainA.SequencerEngine.HTTPEndpoint())) + require.NoError(is.T, supervisorAPI.AddL2RPC(is.T.Ctx(), chainB.SequencerEngine.HTTPEndpoint())) + return &InteropActors{ + L1Miner: l1Miner, + Supervisor: supervisorAPI, + ChainA: chainA, + ChainB: chainB, + } +} + +// SupervisorActor represents a supervisor, instrumented to run synchronously for action-test purposes. +type SupervisorActor struct { + backend *backend.SupervisorBackend + frontend.QueryFrontend + frontend.AdminFrontend + frontend.UpdatesFrontend +} + +func (sa *SupervisorActor) SyncEvents(t helpers.Testing, chainID types.ChainID) { + require.NoError(t, sa.backend.SyncEvents(chainID)) +} + +func (sa *SupervisorActor) SyncCrossUnsafe(t helpers.Testing, chainID types.ChainID) { + require.NoError(t, sa.backend.SyncCrossUnsafe(chainID)) +} + +func (sa *SupervisorActor) SyncCrossSafe(t helpers.Testing, chainID types.ChainID) { + require.NoError(t, sa.backend.SyncCrossSafe(chainID)) +} + +// worldToDepSet converts a set of chain configs into a dependency-set for the supervisor. +func worldToDepSet(t helpers.Testing, worldOutput *interopgen.WorldOutput) *depset.StaticConfigDependencySet { + depSetCfg := make(map[types.ChainID]*depset.StaticConfigDependency) + for _, out := range worldOutput.L2s { + depSetCfg[types.ChainIDFromBig(out.Genesis.Config.ChainID)] = &depset.StaticConfigDependency{ + ChainIndex: types.ChainIndex(out.Genesis.Config.ChainID.Uint64()), + ActivationTime: 0, + HistoryMinTime: 0, + } + } + depSet, err := depset.NewStaticConfigDependencySet(depSetCfg) + require.NoError(t, err) + return depSet +} + +// NewSupervisor creates a new SupervisorActor, to action-test the supervisor with. +func NewSupervisor(t helpers.Testing, logger log.Logger, depSet depset.DependencySetSource) *SupervisorActor { + logger = logger.New("role", "supervisor") + supervisorDataDir := t.TempDir() + logger.Info("supervisor data dir", "dir", supervisorDataDir) + svCfg := &config.Config{ + DependencySetSource: depSet, + SynchronousProcessors: true, + Datadir: supervisorDataDir, + } + b, err := backend.NewSupervisorBackend(t.Ctx(), + logger.New("role", "supervisor"), metrics.NoopMetrics, svCfg) + require.NoError(t, err) + return &SupervisorActor{ + backend: b, + QueryFrontend: frontend.QueryFrontend{ + Supervisor: b, + }, + AdminFrontend: frontend.AdminFrontend{ + Supervisor: b, + }, + UpdatesFrontend: frontend.UpdatesFrontend{ + Supervisor: b, + }, + } +} + +// createL2Services creates a Chain bundle, with the given configs, and attached to the given L1 miner. +func createL2Services( + t helpers.Testing, + logger log.Logger, + l1Miner *helpers.L1Miner, + keys devkeys.Keys, + output *interopgen.L2Output, + interopBackend interop.InteropBackend, +) *Chain { + logger = logger.New("chain", output.Genesis.Config.ChainID) + + jwtPath := e2eutils.WriteDefaultJWT(t) + + eng := helpers.NewL2Engine(t, logger.New("role", "engine"), output.Genesis, jwtPath) + + seqCl, err := sources.NewEngineClient(eng.RPCClient(), logger, nil, sources.EngineClientDefaultConfig(output.RollupCfg)) + require.NoError(t, err) + + l1F, err := sources.NewL1Client(l1Miner.RPCClient(), logger, nil, + sources.L1ClientDefaultConfig(output.RollupCfg, false, sources.RPCKindStandard)) + require.NoError(t, err) + + seq := helpers.NewL2Sequencer(t, logger.New("role", "sequencer"), l1F, + l1Miner.BlobStore(), altda.Disabled, seqCl, output.RollupCfg, + 0, interopBackend) + + batcherKey, err := keys.Secret(devkeys.ChainOperatorKey{ + ChainID: output.Genesis.Config.ChainID, + Role: devkeys.BatcherRole, + }) + require.NoError(t, err) + + batcherCfg := &helpers.BatcherCfg{ + MinL1TxSize: 0, + MaxL1TxSize: 128_000, + BatcherKey: batcherKey, + DataAvailabilityType: batcherFlags.CalldataType, + } + + batcher := helpers.NewL2Batcher(logger.New("role", "batcher"), output.RollupCfg, batcherCfg, + seq.RollupClient(), l1Miner.EthClient(), + eng.EthClient(), eng.EngineClient(t, output.RollupCfg)) + + return &Chain{ + ChainID: types.ChainIDFromBig(output.Genesis.Config.ChainID), + RollupCfg: output.RollupCfg, + ChainCfg: output.Genesis.Config, + BatcherAddr: crypto.PubkeyToAddress(batcherKey.PublicKey), + Sequencer: seq, + SequencerEngine: eng, + Batcher: batcher, + } +} diff --git a/op-e2e/actions/interop/interop_test.go b/op-e2e/actions/interop/interop_test.go index c345404907b02..a39da702db8c5 100644 --- a/op-e2e/actions/interop/interop_test.go +++ b/op-e2e/actions/interop/interop_test.go @@ -1,186 +1,109 @@ package interop import ( - "context" "testing" "github.com/stretchr/testify/require" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" - "github.com/ethereum-optimism/optimism/op-node/rollup/interop" - "github.com/ethereum-optimism/optimism/op-node/rollup/sync" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum-optimism/optimism/op-service/testutils" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) -var _ interop.InteropBackend = (*testutils.FakeInteropBackend)(nil) - -func TestInteropVerifier(gt *testing.T) { +func TestFullInterop(gt *testing.T) { t := helpers.NewDefaultTesting(gt) - dp := e2eutils.MakeDeployParams(t, helpers.DefaultRollupTestParams()) - sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc) - // Temporary work-around: interop needs to be active, for cross-safety to not be instant. - // The state genesis in this test is pre-interop however. - sd.RollupCfg.InteropTime = new(uint64) - logger := testlog.Logger(t, log.LevelDebug) - seqMockBackend := &testutils.FakeInteropBackend{} - l1Miner, seqEng, seq := helpers.SetupSequencerTest(t, sd, logger, - helpers.WithVerifierOpts(helpers.WithInteropBackend(seqMockBackend))) - - batcher := helpers.NewL2Batcher(logger, sd.RollupCfg, helpers.DefaultBatcherCfg(dp), - seq.RollupClient(), l1Miner.EthClient(), seqEng.EthClient(), seqEng.EngineClient(t, sd.RollupCfg)) - - verMockBackend := &testutils.FakeInteropBackend{} - _, ver := helpers.SetupVerifier(t, sd, logger, - l1Miner.L1Client(t, sd.RollupCfg), l1Miner.BlobStore(), &sync.Config{}, - helpers.WithInteropBackend(verMockBackend)) - seq.ActL2PipelineFull(t) - ver.ActL2PipelineFull(t) - - l2ChainID := types.ChainIDFromBig(sd.RollupCfg.L2ChainID) - seqMockBackend.UpdateLocalUnsafeFn = func(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error { - require.Equal(t, chainID, l2ChainID) - require.Equal(t, uint64(1), head.Number) - return nil - } - seqMockBackend.UnsafeViewFn = func(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) { - require.Equal(t, chainID, l2ChainID) - require.Equal(t, uint64(1), unsafe.Local.Number) - require.Equal(t, uint64(0), unsafe.Cross.Number) - return unsafe, nil - } - // create an unsafe L2 block - seq.ActL2StartBlock(t) - seq.ActL2EndBlock(t) - seq.ActL2PipelineFull(t) - status := seq.SyncStatus() - require.Equal(t, uint64(1), status.UnsafeL2.Number) + is := SetupInterop(t) + actors := is.CreateActors() + + // get both sequencers set up + actors.ChainA.Sequencer.ActL2PipelineFull(t) + actors.ChainB.Sequencer.ActL2PipelineFull(t) + + // No blocks yet + status := actors.ChainA.Sequencer.SyncStatus() + require.Equal(t, uint64(0), status.UnsafeL2.Number) + + // sync chain A + actors.Supervisor.SyncEvents(t, actors.ChainA.ChainID) + actors.Supervisor.SyncCrossUnsafe(t, actors.ChainA.ChainID) + actors.Supervisor.SyncCrossSafe(t, actors.ChainA.ChainID) + + // sync chain B + actors.Supervisor.SyncEvents(t, actors.ChainB.ChainID) + actors.Supervisor.SyncCrossUnsafe(t, actors.ChainB.ChainID) + actors.Supervisor.SyncCrossSafe(t, actors.ChainB.ChainID) + + // Build L2 block on chain A + actors.ChainA.Sequencer.ActL2StartBlock(t) + actors.ChainA.Sequencer.ActL2EndBlock(t) + status = actors.ChainA.Sequencer.SyncStatus() + head := status.UnsafeL2.ID() + require.Equal(t, uint64(1), head.Number) require.Equal(t, uint64(0), status.CrossUnsafeL2.Number) require.Equal(t, uint64(0), status.LocalSafeL2.Number) require.Equal(t, uint64(0), status.SafeL2.Number) + require.Equal(t, uint64(0), status.FinalizedL2.Number) - // promote it to cross-unsafe in the backend - // and see if the node picks up on it - seqMockBackend.UnsafeViewFn = func(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) { - require.Equal(t, chainID, l2ChainID) - require.Equal(t, uint64(1), unsafe.Local.Number) - require.Equal(t, uint64(0), unsafe.Cross.Number) - out := unsafe - out.Cross = unsafe.Local - return out, nil - } - seq.ActInteropBackendCheck(t) - seq.ActL2PipelineFull(t) - status = seq.SyncStatus() - require.Equal(t, uint64(1), status.UnsafeL2.Number) - require.Equal(t, uint64(1), status.CrossUnsafeL2.Number, "cross unsafe now") + // Verify as cross-unsafe with supervisor + actors.Supervisor.SyncEvents(t, actors.ChainA.ChainID) + actors.Supervisor.SyncCrossUnsafe(t, actors.ChainA.ChainID) + actors.ChainA.Sequencer.ActL2PipelineFull(t) + status = actors.ChainA.Sequencer.SyncStatus() + require.Equal(t, head, status.UnsafeL2.ID()) + require.Equal(t, head, status.CrossUnsafeL2.ID()) require.Equal(t, uint64(0), status.LocalSafeL2.Number) require.Equal(t, uint64(0), status.SafeL2.Number) + require.Equal(t, uint64(0), status.FinalizedL2.Number) - // submit all new L2 blocks - batcher.ActSubmitAll(t) - // new L1 block with L2 batch - l1Miner.ActL1StartBlock(12)(t) - l1Miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t) - l1Miner.ActL1EndBlock(t) - - // Sync the L1 block, to verify the L2 block as local-safe. - seqMockBackend.UpdateLocalUnsafeFn = nil - seqMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error { - require.Equal(t, uint64(1), lastDerived.Number) - return nil - } - seqMockBackend.SafeViewFn = func(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) { - require.Equal(t, chainID, l2ChainID) - require.Equal(t, uint64(1), safe.Local.Number) - require.Equal(t, uint64(0), safe.Cross.Number) - return safe, nil - } - seq.ActL1HeadSignal(t) - l1Head := seq.SyncStatus().HeadL1 - seq.ActL2PipelineFull(t) - - status = seq.SyncStatus() - require.Equal(t, uint64(1), status.UnsafeL2.Number) - require.Equal(t, uint64(1), status.CrossUnsafeL2.Number) - require.Equal(t, uint64(1), status.LocalSafeL2.Number, "local safe changed") + // Submit the L2 block, sync the local-safe data + actors.ChainA.Batcher.ActSubmitAll(t) + actors.L1Miner.ActL1StartBlock(12)(t) + actors.L1Miner.ActL1IncludeTx(actors.ChainA.BatcherAddr)(t) + actors.L1Miner.ActL1EndBlock(t) + actors.ChainA.Sequencer.ActL1HeadSignal(t) + actors.ChainA.Sequencer.ActL2PipelineFull(t) + status = actors.ChainA.Sequencer.SyncStatus() + require.Equal(t, head, status.UnsafeL2.ID()) + require.Equal(t, head, status.CrossUnsafeL2.ID()) + require.Equal(t, head, status.LocalSafeL2.ID()) require.Equal(t, uint64(0), status.SafeL2.Number) - - // Now mark it as cross-safe - seqMockBackend.SafeViewFn = func(ctx context.Context, chainID types.ChainID, request types.ReferenceView) (types.ReferenceView, error) { - require.Equal(t, chainID, l2ChainID) - require.Equal(t, uint64(1), request.Local.Number) - require.Equal(t, uint64(0), request.Cross.Number) - out := request - out.Cross = request.Local - return out, nil - } - seqMockBackend.DerivedFromFn = func(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) { - require.Equal(t, uint64(1), blockNumber) - return l1Head, nil - } - seqMockBackend.FinalizedFn = func(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) { - return seq.RollupCfg.Genesis.L1, nil - } - seq.ActInteropBackendCheck(t) - seq.ActL2PipelineFull(t) - - status = seq.SyncStatus() - require.Equal(t, uint64(1), status.UnsafeL2.Number) - require.Equal(t, uint64(1), status.CrossUnsafeL2.Number) - require.Equal(t, uint64(1), status.LocalSafeL2.Number) - require.Equal(t, uint64(1), status.SafeL2.Number, "cross-safe reached") require.Equal(t, uint64(0), status.FinalizedL2.Number) - - verMockBackend.UpdateLocalUnsafeFn = func(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error { - require.Equal(t, uint64(1), head.Number) - return nil - } - verMockBackend.UpdateLocalSafeFn = func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error { - require.Equal(t, uint64(1), lastDerived.Number) - require.Equal(t, l1Head.ID(), derivedFrom.ID()) - return nil - } - // The verifier might not see the L2 block that was just derived from L1 as cross-verified yet. - verMockBackend.UnsafeViewFn = func(ctx context.Context, chainID types.ChainID, request types.ReferenceView) (types.ReferenceView, error) { - require.Equal(t, uint64(1), request.Local.Number) - require.Equal(t, uint64(0), request.Cross.Number) - // Don't promote the Cross value yet - return request, nil - } - verMockBackend.SafeViewFn = func(ctx context.Context, chainID types.ChainID, request types.ReferenceView) (types.ReferenceView, error) { - require.Equal(t, uint64(1), request.Local.Number) - require.Equal(t, uint64(0), request.Cross.Number) - // Don't promote the Cross value yet - return request, nil - } - ver.ActL1HeadSignal(t) - ver.ActL2PipelineFull(t) - status = ver.SyncStatus() - require.Equal(t, uint64(1), status.UnsafeL2.Number, "synced the block") - require.Equal(t, uint64(0), status.CrossUnsafeL2.Number, "not cross-verified yet") - require.Equal(t, uint64(1), status.LocalSafeL2.Number, "derived from L1, thus local-safe") - require.Equal(t, uint64(0), status.SafeL2.Number, "not yet cross-safe") + // Local-safe does not count as "safe" in RPC + n := actors.ChainA.SequencerEngine.L2Chain().CurrentSafeBlock().Number.Uint64() + require.Equal(t, uint64(0), n) + + // Cross-safe verify it + actors.Supervisor.SyncCrossSafe(t, actors.ChainA.ChainID) + actors.ChainA.Sequencer.ActInteropBackendCheck(t) + actors.ChainA.Sequencer.ActL2PipelineFull(t) + status = actors.ChainA.Sequencer.SyncStatus() + require.Equal(t, head, status.UnsafeL2.ID()) + require.Equal(t, head, status.CrossUnsafeL2.ID()) + require.Equal(t, head, status.LocalSafeL2.ID()) + require.Equal(t, head, status.SafeL2.ID()) require.Equal(t, uint64(0), status.FinalizedL2.Number) - - seqMockBackend.UpdateFinalizedL1Fn = func(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error { - require.Equal(t, l1Head, finalized) - return nil - } - // signal that L1 finalized; the cross-safe block we have should get finalized too - l1Miner.ActL1SafeNext(t) - l1Miner.ActL1FinalizeNext(t) - seq.ActL1SafeSignal(t) - seq.ActL1FinalizedSignal(t) - seq.ActL2PipelineFull(t) - - status = seq.SyncStatus() - require.Equal(t, uint64(1), status.FinalizedL2.Number, "finalized the block") + h := actors.ChainA.SequencerEngine.L2Chain().CurrentSafeBlock().Hash() + require.Equal(t, head.Hash, h) + + // Finalize L1, and see how the op-node forwards it to the supervisor. + // The supervisor then determines finality, which the op-node can use. + actors.L1Miner.ActL1SafeNext(t) + actors.L1Miner.ActL1FinalizeNext(t) + actors.ChainA.Sequencer.ActL1SafeSignal(t) + actors.ChainA.Sequencer.ActL1FinalizedSignal(t) + actors.ChainA.Sequencer.ActL2PipelineFull(t) + finalizedL2BlockID, err := actors.Supervisor.Finalized(t.Ctx(), actors.ChainA.ChainID) + require.NoError(t, err) + require.Equal(t, head, finalizedL2BlockID) + + // The op-node needs a poke to look at the updated supervisor finality state + actors.ChainA.Sequencer.ActInteropBackendCheck(t) + actors.ChainA.Sequencer.ActL2PipelineFull(t) + h = actors.ChainA.SequencerEngine.L2Chain().CurrentFinalBlock().Hash() + require.Equal(t, head.Hash, h) + status = actors.ChainA.Sequencer.SyncStatus() + require.Equal(t, head, status.UnsafeL2.ID()) + require.Equal(t, head, status.CrossUnsafeL2.ID()) + require.Equal(t, head, status.LocalSafeL2.ID()) + require.Equal(t, head, status.SafeL2.ID()) + require.Equal(t, head, status.FinalizedL2.ID()) } diff --git a/op-e2e/actions/proofs/bad_tx_in_batch_test.go b/op-e2e/actions/proofs/bad_tx_in_batch_test.go index f67e216f76cb0..ce6e37469c1b3 100644 --- a/op-e2e/actions/proofs/bad_tx_in_batch_test.go +++ b/op-e2e/actions/proofs/bad_tx_in_batch_test.go @@ -26,14 +26,14 @@ func runBadTxInBatchTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { env.Alice.L2.ActCheckReceiptStatusOfLastTx(true)(t) // Instruct the batcher to submit a faulty channel, with an invalid tx. - err := env.Batcher.Buffer(t, func(block *types.Block) { + env.Batcher.ActL2BatchBuffer(t, func(block *types.Block) *types.Block { // Replace the tx with one that has a bad signature. txs := block.Transactions() newTx, err := txs[1].WithSignature(env.Alice.L2.Signer(), make([]byte, 65)) txs[1] = newTx require.NoError(t, err) + return block }) - require.NoError(t, err) env.Batcher.ActL2ChannelClose(t) env.Batcher.ActL2BatchSubmit(t) @@ -91,12 +91,13 @@ func runBadTxInBatch_ResubmitBadFirstFrame_Test(gt *testing.T, testCfg *helpers. // Instruct the batcher to submit a faulty channel, with an invalid tx in the second block // within the span batch. env.Batcher.ActL2BatchBuffer(t) - err := env.Batcher.Buffer(t, func(block *types.Block) { + err := env.Batcher.Buffer(t, func(block *types.Block) *types.Block { // Replace the tx with one that has a bad signature. txs := block.Transactions() newTx, err := txs[1].WithSignature(env.Alice.L2.Signer(), make([]byte, 65)) txs[1] = newTx require.NoError(t, err) + return block }) require.NoError(t, err) env.Batcher.ActL2ChannelClose(t) @@ -144,14 +145,14 @@ func Test_ProgramAction_BadTxInBatch(gt *testing.T) { matrix.AddTestCase( "HonestClaim", nil, - helpers.LatestForkOnly, + helpers.NewForkMatrix(helpers.Granite), runBadTxInBatchTest, helpers.ExpectNoError(), ) matrix.AddTestCase( "JunkClaim", nil, - helpers.LatestForkOnly, + helpers.NewForkMatrix(helpers.Granite), runBadTxInBatchTest, helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), @@ -159,14 +160,14 @@ func Test_ProgramAction_BadTxInBatch(gt *testing.T) { matrix.AddTestCase( "ResubmitBadFirstFrame-HonestClaim", nil, - helpers.LatestForkOnly, + helpers.NewForkMatrix(helpers.Granite), runBadTxInBatch_ResubmitBadFirstFrame_Test, helpers.ExpectNoError(), ) matrix.AddTestCase( "ResubmitBadFirstFrame-JunkClaim", nil, - helpers.LatestForkOnly, + helpers.NewForkMatrix(helpers.Granite), runBadTxInBatch_ResubmitBadFirstFrame_Test, helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), diff --git a/op-e2e/actions/proofs/helpers/env.go b/op-e2e/actions/proofs/helpers/env.go index de18c8cbce939..7027647e142b2 100644 --- a/op-e2e/actions/proofs/helpers/env.go +++ b/op-e2e/actions/proofs/helpers/env.go @@ -4,6 +4,7 @@ import ( "context" "math/rand" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" e2ecfg "github.com/ethereum-optimism/optimism/op-e2e/config" altda "github.com/ethereum-optimism/optimism/op-alt-da" @@ -28,6 +29,7 @@ import ( // L2FaultProofEnv is a test harness for a fault provable L2 chain. type L2FaultProofEnv struct { log log.Logger + Logs *testlog.CapturingHandler Batcher *helpers.L2Batcher Sequencer *helpers.L2Sequencer Engine *helpers.L2Engine @@ -39,8 +41,11 @@ type L2FaultProofEnv struct { Bob *helpers.CrossLayerUser } -func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eutils.TestParams, batcherCfg *helpers.BatcherCfg) *L2FaultProofEnv { - log := testlog.Logger(t, log.LvlDebug) +type deployConfigOverride func(*genesis.DeployConfig) + +func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eutils.TestParams, batcherCfg *helpers.BatcherCfg, deployConfigOverrides ...deployConfigOverride) *L2FaultProofEnv { + log, logs := testlog.CaptureLogger(t, log.LevelDebug) + dp := NewDeployParams(t, tp, func(dp *e2eutils.DeployParams) { genesisBlock := hexutil.Uint64(0) @@ -61,6 +66,12 @@ func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eut dp.DeployConfig.L2GenesisFjordTimeOffset = &genesisBlock case Granite: dp.DeployConfig.L2GenesisGraniteTimeOffset = &genesisBlock + case Holocene: + dp.DeployConfig.L2GenesisHoloceneTimeOffset = &genesisBlock + } + + for _, override := range deployConfigOverrides { + override(dp.DeployConfig) } }) sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc) @@ -72,7 +83,7 @@ func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eut l1Cl, err := sources.NewL1Client(miner.RPCClient(), log, nil, sources.L1ClientDefaultConfig(sd.RollupCfg, false, sources.RPCKindStandard)) require.NoError(t, err) - engine := helpers.NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath, helpers.EngineWithP2P()) + engine := helpers.NewL2Engine(t, log.New("role", "sequencer-engine"), sd.L2Cfg, jwtPath, helpers.EngineWithP2P()) l2EngineCl, err := sources.NewEngineClient(engine.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg)) require.NoError(t, err) @@ -109,6 +120,7 @@ func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eut return &L2FaultProofEnv{ log: log, + Logs: logs, Batcher: batcher, Sequencer: sequencer, Engine: engine, @@ -143,9 +155,20 @@ func WithL2Claim(claim common.Hash) FixtureInputParam { } } +func WithL2BlockNumber(num uint64) FixtureInputParam { + return func(f *FixtureInputs) { + f.L2BlockNumber = num + } +} + func (env *L2FaultProofEnv) RunFaultProofProgram(t helpers.Testing, l2ClaimBlockNum uint64, checkResult CheckResult, fixtureInputParams ...FixtureInputParam) { // Fetch the pre and post output roots for the fault proof. - preRoot, err := env.Sequencer.RollupClient().OutputAtBlock(t.Ctx(), l2ClaimBlockNum-1) + l2PreBlockNum := l2ClaimBlockNum - 1 + if l2ClaimBlockNum == 0 { + // If we are at genesis, we assert that we don't move the chain at all. + l2PreBlockNum = 0 + } + preRoot, err := env.Sequencer.RollupClient().OutputAtBlock(t.Ctx(), l2PreBlockNum) require.NoError(t, err) claimRoot, err := env.Sequencer.RollupClient().OutputAtBlock(t.Ctx(), l2ClaimBlockNum) require.NoError(t, err) @@ -193,7 +216,7 @@ func (env *L2FaultProofEnv) RunFaultProofProgram(t helpers.Testing, l2ClaimBlock l2RPC := env.Engine.RPCClient() l2Client, err := host.NewL2Client(l2RPC, env.log, nil, &host.L2ClientConfig{L2ClientConfig: l2ClCfg, L2Head: cfg.L2Head}) require.NoError(t, err, "failed to create L2 client") - l2DebugCl := &host.L2Source{L2Client: l2Client, DebugClient: sources.NewDebugClient(l2RPC.CallContext)} + l2DebugCl := host.NewL2SourceWithClient(logger, l2Client, sources.NewDebugClient(l2RPC.CallContext)) return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv), nil }) diff --git a/op-e2e/actions/proofs/helpers/kona.go b/op-e2e/actions/proofs/helpers/kona.go index 9d34a98dda01f..dc9b98cd80978 100644 --- a/op-e2e/actions/proofs/helpers/kona.go +++ b/op-e2e/actions/proofs/helpers/kona.go @@ -16,15 +16,14 @@ import ( "github.com/stretchr/testify/require" ) -var konaHostPath, konaClientPath string +var konaHostPath string func init() { konaHostPath = os.Getenv("KONA_HOST_PATH") - konaClientPath = os.Getenv("KONA_CLIENT_PATH") } func IsKonaConfigured() bool { - return konaHostPath != "" && konaClientPath != "" + return konaHostPath != "" } func RunKonaNative( @@ -57,7 +56,7 @@ func RunKonaNative( L2Claim: fixtureInputs.L2Claim, L2BlockNumber: big.NewInt(int64(fixtureInputs.L2BlockNumber)), } - hostCmd, err := vm.NewNativeKonaExecutor(konaClientPath).OracleCommand(vmCfg, workDir, inputs) + hostCmd, err := vm.NewNativeKonaExecutor().OracleCommand(vmCfg, workDir, inputs) require.NoError(t, err) cmd := exec.Command(hostCmd[0], hostCmd[1:]...) diff --git a/op-e2e/actions/proofs/helpers/matrix.go b/op-e2e/actions/proofs/helpers/matrix.go index 7f3e810e86b6c..ff6bf2ad77b64 100644 --- a/op-e2e/actions/proofs/helpers/matrix.go +++ b/op-e2e/actions/proofs/helpers/matrix.go @@ -84,13 +84,17 @@ var ( Regolith = &Hardfork{Name: "Regolith", Precedence: 1} Canyon = &Hardfork{Name: "Canyon", Precedence: 2} Delta = &Hardfork{Name: "Delta", Precedence: 3} - Fjord = &Hardfork{Name: "Fjord", Precedence: 4} - Ecotone = &Hardfork{Name: "Ecotone", Precedence: 5} + Ecotone = &Hardfork{Name: "Ecotone", Precedence: 4} + Fjord = &Hardfork{Name: "Fjord", Precedence: 5} Granite = &Hardfork{Name: "Granite", Precedence: 6} + Holocene = &Hardfork{Name: "Holocene", Precedence: 7} ) -var Hardforks = ForkMatrix{Regolith, Canyon, Delta, Fjord, Ecotone, Granite} -var LatestForkOnly = ForkMatrix{Hardforks[len(Hardforks)-1]} +var ( + Hardforks = ForkMatrix{Regolith, Canyon, Delta, Ecotone, Fjord, Granite, Holocene} + LatestFork = Hardforks[len(Hardforks)-1] + LatestForkOnly = ForkMatrix{LatestFork} +) func NewForkMatrix(forks ...*Hardfork) ForkMatrix { return append(ForkMatrix{}, forks...) diff --git a/op-e2e/actions/proofs/holocene_activation_test.go b/op-e2e/actions/proofs/holocene_activation_test.go new file mode 100644 index 0000000000000..1cdd3aba45737 --- /dev/null +++ b/op-e2e/actions/proofs/holocene_activation_test.go @@ -0,0 +1,130 @@ +package proofs + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +func Test_ProgramAction_HoloceneActivation(gt *testing.T) { + + runHoloceneDerivationTest := func(gt *testing.T, testCfg *helpers.TestCfg[any]) { + t := actionsHelpers.NewDefaultTesting(gt) + + // Define override to activate Holocene 14 seconds after genesis + var setHoloceneTime = func(dc *genesis.DeployConfig) { + fourteen := hexutil.Uint64(14) + dc.L2GenesisHoloceneTimeOffset = &fourteen + } + + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg(), setHoloceneTime) + + t.Logf("L2 Genesis Time: %d, HoloceneTime: %d ", env.Sequencer.RollupCfg.Genesis.L2Time, *env.Sequencer.RollupCfg.HoloceneTime) + + // Build the L2 chain until the Holocene activation time, + // which for the Execution Engine is an L2 block timestamp + // https://specs.optimism.io/protocol/holocene/exec-engine.html?highlight=holocene#timestamp-activation + for env.Engine.L2Chain().CurrentBlock().Time < *env.Sequencer.RollupCfg.HoloceneTime { + b := env.Engine.L2Chain().GetBlockByHash(env.Sequencer.L2Unsafe().Hash) + require.Equal(t, "", string(b.Extra()), "extra data should be empty before Holocene activation") + env.Sequencer.ActL2StartBlock(t) + // Send an L2 tx + env.Alice.L2.ActResetTxOpts(t) + env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob) + env.Alice.L2.ActMakeTx(t) + env.Engine.ActL2IncludeTx(env.Alice.Address())(t) + env.Sequencer.ActL2EndBlock(t) + t.Log("Unsafe block with timestamp %d", b.Time) + } + b := env.Engine.L2Chain().GetBlockByHash(env.Sequencer.L2Unsafe().Hash) + require.Len(t, b.Extra(), 9, "extra data should be 9 bytes after Holocene activation") + + // Build up a local list of frames + orderedFrames := make([][]byte, 0, 1) + // Submit the first two blocks, this will be enough to trigger Holocene _derivation_ + // which is activated by the L1 inclusion block timestamp + // https://specs.optimism.io/protocol/holocene/derivation.html?highlight=holoce#activation + // block 1 will be 12 seconda after genesis, and 2 seconds before Holocene activation + // block 2 will be 24 seconds after genesis, and 10 seconds after Holocene activation + blocksToSubmit := []uint{1, 2} + // Buffer the blocks in the batcher and populate orderedFrames list + env.Batcher.ActCreateChannel(t, false) + for i, blockNum := range blocksToSubmit { + env.Batcher.ActAddBlockByNumber(t, int64(blockNum), actionsHelpers.BlockLogger(t)) + if i == len(blocksToSubmit)-1 { + env.Batcher.ActL2ChannelClose(t) + } + frame := env.Batcher.ReadNextOutputFrame(t) + require.NotEmpty(t, frame, "frame %d", i) + orderedFrames = append(orderedFrames, frame) + } + + includeBatchTx := func() { + // Include the last transaction submitted by the batcher. + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t) + env.Miner.ActL1EndBlock(t) + } + + // Submit first frame + env.Batcher.ActL2BatchSubmitRaw(t, orderedFrames[0]) + includeBatchTx() // L1 block should have a timestamp of 12s after genesis + + // Holocene should activate 14s after genesis, so that the previous l1 block + // was before HoloceneTime and the next l1 block is after it + + // Submit final frame + env.Batcher.ActL2BatchSubmitRaw(t, orderedFrames[1]) + includeBatchTx() // block should have a timestamp of 24s after genesis + + // Instruct the sequencer to derive the L2 chain from the data on L1 that the batcher just posted. + env.Sequencer.ActL1HeadSignal(t) + env.Sequencer.ActL2PipelineFull(t) + + l2SafeHead := env.Sequencer.L2Safe() + t.Log(l2SafeHead.Time) + require.EqualValues(t, uint64(0), l2SafeHead.Number) // channel should be dropped, so no safe head progression + if uint64(0) == l2SafeHead.Number { + t.Log("Safe head progressed as expected", "l2SafeHeadNumber", l2SafeHead.Number) + } + + // Log assertions + filters := []string{ + "FrameQueue: resetting with Holocene activation", + "ChannelMux: transforming to Holocene stage", + "BatchMux: transforming to Holocene stage", + "dropping non-first frame without channel", + } + for _, filter := range filters { + recs := env.Logs.FindLogs(testlog.NewMessageContainsFilter(filter), testlog.NewAttributesFilter("role", "sequencer")) + require.Len(t, recs, 1, "searching for %d instances of '%s' in logs from role %s", 1, filter, "sequencer") + } + env.RunFaultProofProgram(t, l2SafeHead.Number, testCfg.CheckResult, testCfg.InputParams...) + } + + matrix := helpers.NewMatrix[any]() + defer matrix.Run(gt) + + matrix.AddTestCase( + "HonestClaim-HoloceneActivation", + nil, + helpers.NewForkMatrix(helpers.Granite), + runHoloceneDerivationTest, + helpers.ExpectNoError(), + ) + matrix.AddTestCase( + "JunkClaim-HoloceneActivation", + nil, + helpers.NewForkMatrix(helpers.Granite), + runHoloceneDerivationTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), + ) +} diff --git a/op-e2e/actions/proofs/holocene_batches_test.go b/op-e2e/actions/proofs/holocene_batches_test.go new file mode 100644 index 0000000000000..8028aa0f5b905 --- /dev/null +++ b/op-e2e/actions/proofs/holocene_batches_test.go @@ -0,0 +1,155 @@ +package proofs + +import ( + "fmt" + "testing" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func Test_ProgramAction_HoloceneBatches(gt *testing.T) { + type testCase struct { + name string + blocks []uint // blocks is an ordered list of blocks (by number) to add to a single channel. + isSpanBatch bool + holoceneExpectations + } + + // Depending on the blocks list, we expect a different + // progression of the safe head under Holocene + // derivation rules, compared with pre Holocene. + testCases := []testCase{ + // Standard channel composition + { + name: "ordered", blocks: []uint{1, 2, 3}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, + holocene: expectations{safeHead: 3}, + }, + }, + + // Non-standard channel composition + { + name: "disordered-a", blocks: []uint{1, 3, 2}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, // batches are buffered, so the block ordering does not matter + holocene: expectations{safeHead: 1, // batch for block 3 is considered invalid because it is from the future. This batch + remaining channel is dropped. + logs: append( + sequencerOnce("dropping future batch"), + sequencerOnce("Dropping invalid singular batch, flushing channel")..., + )}, + }, + }, + { + name: "disordered-b", blocks: []uint{2, 1, 3}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, // batches are buffered, so the block ordering does not matter + holocene: expectations{safeHead: 0, // batch for block 2 is considered invalid because it is from the future. This batch + remaining channel is dropped. + logs: append( + sequencerOnce("dropping future batch"), + sequencerOnce("Dropping invalid singular batch, flushing channel")..., + )}, + }, + }, + + { + name: "duplicates-a", blocks: []uint{1, 1, 2, 3}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, // duplicate batches are dropped, so this reduces to the "ordered" case + holocene: expectations{safeHead: 3, // duplicate batches are dropped, so this reduces to the "ordered" case + logs: sequencerOnce("dropping past batch with old timestamp")}, + }, + }, + { + name: "duplicates-b", blocks: []uint{2, 2, 1, 3}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, // duplicate batches are silently dropped, so this reduces to disordered-2b + holocene: expectations{safeHead: 0, // duplicate batches are silently dropped, so this reduces to disordered-2b + logs: append( + sequencerOnce("dropping future batch"), + sequencerOnce("Dropping invalid singular batch, flushing channel")..., + )}, + }, + }, + } + + runHoloceneDerivationTest := func(gt *testing.T, testCfg *helpers.TestCfg[testCase]) { + t := actionsHelpers.NewDefaultTesting(gt) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + includeBatchTx := func() { + // Include the last transaction submitted by the batcher. + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t) + env.Miner.ActL1EndBlock(t) + } + + max := func(input []uint) uint { + max := uint(0) + for _, val := range input { + if val > max { + max = val + } + } + return max + } + + targetHeadNumber := max(testCfg.Custom.blocks) + for env.Engine.L2Chain().CurrentBlock().Number.Uint64() < uint64(targetHeadNumber) { + env.Sequencer.ActL2StartBlock(t) + // Send an L2 tx + env.Alice.L2.ActResetTxOpts(t) + env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob) + env.Alice.L2.ActMakeTx(t) + env.Engine.ActL2IncludeTx(env.Alice.Address())(t) + env.Sequencer.ActL2EndBlock(t) + } + + // Buffer the blocks in the batcher. + env.Batcher.ActCreateChannel(t, testCfg.Custom.isSpanBatch) + for _, blockNum := range testCfg.Custom.blocks { + env.Batcher.ActAddBlockByNumber(t, int64(blockNum), actionsHelpers.BlockLogger(t)) + } + env.Batcher.ActL2ChannelClose(t) + frame := env.Batcher.ReadNextOutputFrame(t) + require.NotEmpty(t, frame) + env.Batcher.ActL2BatchSubmitRaw(t, frame) + includeBatchTx() + + // Instruct the sequencer to derive the L2 chain from the data on L1 that the batcher just posted. + env.Sequencer.ActL1HeadSignal(t) + env.Sequencer.ActL2PipelineFull(t) + + l2SafeHead := env.Sequencer.L2Safe() + isHolocene := testCfg.Hardfork.Precedence >= helpers.Holocene.Precedence + testCfg.Custom.RequireExpectedProgressAndLogs(t, l2SafeHead, isHolocene, env.Engine, env.Logs) + t.Log("Safe head progressed as expected", "l2SafeHeadNumber", l2SafeHead.Number) + + env.RunFaultProofProgram(t, l2SafeHead.Number, testCfg.CheckResult, testCfg.InputParams...) + } + + matrix := helpers.NewMatrix[testCase]() + defer matrix.Run(gt) + + for _, ordering := range testCases { + matrix.AddTestCase( + fmt.Sprintf("HonestClaim-%s", ordering.name), + ordering, + helpers.NewForkMatrix(helpers.Granite, helpers.LatestFork), + runHoloceneDerivationTest, + helpers.ExpectNoError(), + ) + matrix.AddTestCase( + fmt.Sprintf("JunkClaim-%s", ordering.name), + ordering, + helpers.NewForkMatrix(helpers.Granite, helpers.LatestFork), + runHoloceneDerivationTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), + ) + } +} diff --git a/op-e2e/actions/proofs/holocene_frame_test.go b/op-e2e/actions/proofs/holocene_frame_test.go new file mode 100644 index 0000000000000..2eb843d62ef83 --- /dev/null +++ b/op-e2e/actions/proofs/holocene_frame_test.go @@ -0,0 +1,138 @@ +package proofs + +import ( + "fmt" + "testing" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func Test_ProgramAction_HoloceneFrames(gt *testing.T) { + type testCase struct { + name string + frames []uint + holoceneExpectations + } + + // An ordered list of frames to read from the channel and submit + // on L1. We expect a different progression of the safe head under Holocene + // derivation rules, compared with pre Holocene. + testCases := []testCase{ + // Standard frame submission, + { + name: "ordered", frames: []uint{0, 1, 2}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, + holocene: expectations{safeHead: 3}, + }, + }, + + // Non-standard frame submission + { + name: "disordered-a", frames: []uint{2, 1, 0}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, // frames are buffered, so ordering does not matter + holocene: expectations{safeHead: 0}, // non-first frames will be dropped b/c it is the first seen with that channel Id. The safe head won't move until the channel is closed/completed. + }, + }, + { + name: "disordered-b", frames: []uint{0, 1, 0, 2}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, // frames are buffered, so ordering does not matter + holocene: expectations{safeHead: 0}, // non-first frames will be dropped b/c it is the first seen with that channel Id. The safe head won't move until the channel is closed/completed. + }, + }, + { + name: "duplicates", frames: []uint{0, 1, 1, 2}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, // frames are buffered, so ordering does not matter + holocene: expectations{safeHead: 3}, // non-contiguous frames are dropped. So this reduces to case-0. + }, + }, + } + + runHoloceneDerivationTest := func(gt *testing.T, testCfg *helpers.TestCfg[testCase]) { + t := actionsHelpers.NewDefaultTesting(gt) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + blocks := []uint{1, 2, 3} + targetHeadNumber := 3 + for env.Engine.L2Chain().CurrentBlock().Number.Uint64() < uint64(targetHeadNumber) { + env.Sequencer.ActL2StartBlock(t) + // Send an L2 tx + env.Alice.L2.ActResetTxOpts(t) + env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob) + env.Alice.L2.ActMakeTx(t) + env.Engine.ActL2IncludeTx(env.Alice.Address())(t) + env.Sequencer.ActL2EndBlock(t) + } + + // Build up a local list of frames + orderedFrames := make([][]byte, 0, len(testCfg.Custom.frames)) + // Buffer the blocks in the batcher and populat orderedFrames list + env.Batcher.ActCreateChannel(t, false) + for i, blockNum := range blocks { + env.Batcher.ActAddBlockByNumber(t, int64(blockNum), actionsHelpers.BlockLogger(t)) + if i == len(blocks)-1 { + env.Batcher.ActL2ChannelClose(t) + } + frame := env.Batcher.ReadNextOutputFrame(t) + require.NotEmpty(t, frame, "frame %d", i) + orderedFrames = append(orderedFrames, frame) + } + + includeBatchTx := func() { + // Include the last transaction submitted by the batcher. + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t) + env.Miner.ActL1EndBlock(t) + + // Finalize the block with the first channel frame on L1. + env.Miner.ActL1SafeNext(t) + env.Miner.ActL1FinalizeNext(t) + } + + // Submit frames in specified order order + for _, j := range testCfg.Custom.frames { + env.Batcher.ActL2BatchSubmitRaw(t, orderedFrames[j]) + includeBatchTx() + } + + // Instruct the sequencer to derive the L2 chain from the data on L1 that the batcher just posted. + env.Sequencer.ActL1HeadSignal(t) + env.Sequencer.ActL2PipelineFull(t) + + l2SafeHead := env.Sequencer.L2Safe() + + isHolocene := testCfg.Hardfork.Precedence >= helpers.Holocene.Precedence + testCfg.Custom.RequireExpectedProgressAndLogs(t, l2SafeHead, isHolocene, env.Engine, env.Logs) + t.Log("Safe head progressed as expected", "l2SafeHeadNumber", l2SafeHead.Number) + + env.RunFaultProofProgram(t, l2SafeHead.Number, testCfg.CheckResult, testCfg.InputParams...) + } + + matrix := helpers.NewMatrix[testCase]() + defer matrix.Run(gt) + + for _, ordering := range testCases { + matrix.AddTestCase( + fmt.Sprintf("HonestClaim-%s", ordering.name), + ordering, + helpers.NewForkMatrix(helpers.Granite, helpers.LatestFork), + runHoloceneDerivationTest, + helpers.ExpectNoError(), + ) + matrix.AddTestCase( + fmt.Sprintf("JunkClaim-%s", ordering.name), + ordering, + helpers.NewForkMatrix(helpers.Granite, helpers.LatestFork), + runHoloceneDerivationTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), + ) + } +} diff --git a/op-e2e/actions/proofs/holocene_helpers_test.go b/op-e2e/actions/proofs/holocene_helpers_test.go new file mode 100644 index 0000000000000..4b19185fe357e --- /dev/null +++ b/op-e2e/actions/proofs/holocene_helpers_test.go @@ -0,0 +1,45 @@ +package proofs + +import ( + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/stretchr/testify/require" +) + +type logExpectations struct { + role string + filter string + num int +} +type expectations struct { + safeHead uint64 + logs []logExpectations +} +type holoceneExpectations struct { + preHolocene, holocene expectations +} + +func (h holoceneExpectations) RequireExpectedProgressAndLogs(t actionsHelpers.StatefulTesting, actualSafeHead eth.L2BlockRef, isHolocene bool, engine *actionsHelpers.L2Engine, logs *testlog.CapturingHandler) { + var exp expectations + if isHolocene { + exp = h.holocene + } else { + exp = h.preHolocene + } + + require.Equal(t, exp.safeHead, actualSafeHead.Number) + expectedHash := engine.L2Chain().GetBlockByNumber(exp.safeHead).Hash() + require.Equal(t, expectedHash, actualSafeHead.Hash) + + for _, l := range exp.logs { + t.Helper() + recs := logs.FindLogs(testlog.NewMessageContainsFilter(l.filter), testlog.NewAttributesFilter("role", l.role)) + require.Len(t, recs, l.num, "searching for %d instances of '%s' in logs from role %s", l.num, l.filter, l.role) + } + +} + +func sequencerOnce(filter string) []logExpectations { + return []logExpectations{{filter: filter, role: "sequencer", num: 1}} +} diff --git a/op-e2e/actions/proofs/holocene_invalid_batch_test.go b/op-e2e/actions/proofs/holocene_invalid_batch_test.go new file mode 100644 index 0000000000000..202861401c9cb --- /dev/null +++ b/op-e2e/actions/proofs/holocene_invalid_batch_test.go @@ -0,0 +1,258 @@ +package proofs + +import ( + "fmt" + "math/big" + "testing" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func Test_ProgramAction_HoloceneInvalidBatch(gt *testing.T) { + type testCase struct { + name string + blocks []uint // An ordered list of blocks (by number) to add to a single channel. + useSpanBatch bool + blockModifiers []actionsHelpers.BlockModifier + breachMaxSequencerDrift bool + overAdvanceL1Origin int // block number at which to over-advance + holoceneExpectations + } + + // invalidPayload invalidates the signature for the second transaction in the block. + // This should result in an invalid payload in the engine queue. + invalidPayload := func(block *types.Block) *types.Block { + alice := types.NewCancunSigner(big.NewInt(901)) + txs := block.Transactions() + newTx, err := txs[1].WithSignature(alice, make([]byte, 65)) + if err != nil { + panic(err) + } + txs[1] = newTx + return block + } + + // invalidParentHash invalidates the parentHash of the block. + // This should result in an invalid batch being derived, + // but only for singular (not for span) batches. + invalidParentHash := func(block *types.Block) *types.Block { + headerCopy := block.Header() + headerCopy.ParentHash = common.MaxHash + return block.WithSeal(headerCopy) + } + + k := 2000 + twoThousandBlocks := make([]uint, k) + for i := 0; i < k; i++ { + twoThousandBlocks[i] = uint(i) + 1 + } + + // Depending on the blocks list, whether the channel is built as + // as span batch channel, and whether the blocks are modified / invalidated + // we expect a different progression of the safe head under Holocene + // derivation rules, compared with pre Holocene. + testCases := []testCase{ + // Standard frame submission, standard channel composition + { + name: "valid", blocks: []uint{1, 2, 3}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 3}, holocene: expectations{safeHead: 3}, + }, + }, + + { + name: "invalid-payload", blocks: []uint{1, 2, 3}, blockModifiers: []actionsHelpers.BlockModifier{nil, invalidPayload, nil}, + useSpanBatch: false, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 1, // Invalid signature in block 2 causes an invalid _payload_ in the engine queue. Entire span batch is invalidated. + logs: sequencerOnce("could not process payload attributes"), + }, + holocene: expectations{safeHead: 2, // We expect the safe head to move to 2 due to creation of a deposit-only block. + logs: append( + sequencerOnce("Holocene active, requesting deposits-only attributes"), + sequencerOnce("could not process payload attributes")..., + ), + }, + }, + }, + { + name: "invalid-payload-span", blocks: []uint{1, 2, 3}, blockModifiers: []actionsHelpers.BlockModifier{nil, invalidPayload, nil}, + useSpanBatch: true, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 0, // Invalid signature in block 2 causes an invalid _payload_ in the engine queue. Entire span batch is invalidated. + logs: sequencerOnce("could not process payload attributes"), + }, + + holocene: expectations{safeHead: 2, // We expect the safe head to move to 2 due to creation of an deposit-only block. + logs: sequencerOnce("could not process payload attributes"), + }, + }, + }, + + { + name: "invalid-parent-hash", blocks: []uint{1, 2, 3}, blockModifiers: []actionsHelpers.BlockModifier{nil, invalidParentHash, nil}, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 1, // Invalid parentHash in block 2 causes an invalid batch to be dropped. + logs: sequencerOnce("ignoring batch with mismatching parent hash")}, + holocene: expectations{safeHead: 1, // Same with Holocene. + logs: sequencerOnce("Dropping invalid singular batch, flushing channel")}, + }, + }, + { + name: "seq-drift-span", blocks: twoThousandBlocks, // if we artificially stall the l1 origin, this should be enough to trigger violation of the max sequencer drift + useSpanBatch: true, + breachMaxSequencerDrift: true, + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 0, // Entire span batch invalidated. + logs: sequencerOnce("batch exceeded sequencer time drift without adopting next origin, and next L1 origin would have been valid"), + }, + holocene: expectations{safeHead: 1800, // We expect partial validity until we hit sequencer drift. + logs: sequencerOnce("batch exceeded sequencer time drift without adopting next origin, and next L1 origin would have been valid"), + }, + }, + }, + { + name: "future-l1-origin-span", + blocks: []uint{1, 2, 3, 4}, + useSpanBatch: true, + overAdvanceL1Origin: 3, // this will over-advance the L1 origin of block 3 + holoceneExpectations: holoceneExpectations{ + preHolocene: expectations{safeHead: 0, // Entire span batch invalidated. + logs: sequencerOnce("block timestamp is less than L1 origin timestamp"), + }, + holocene: expectations{safeHead: 2, // We expect partial validity, safe head should move to block 2, dropping invalid block 3 and remaining channel. + logs: sequencerOnce("batch timestamp is less than L1 origin timestamp"), + }, + }, + }, + } + + runHoloceneDerivationTest := func(gt *testing.T, testCfg *helpers.TestCfg[testCase]) { + t := actionsHelpers.NewDefaultTesting(gt) + tp := helpers.NewTestParams(func(tp *e2eutils.TestParams) { + // Set the channel timeout to 10 blocks, 12x lower than the sequencing window. + tp.ChannelTimeout = 10 + }) + + env := helpers.NewL2FaultProofEnv(t, testCfg, tp, helpers.NewBatcherCfg()) + + includeBatchTx := func() { + // Include the last transaction submitted by the batcher. + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t) + env.Miner.ActL1EndBlock(t) + // Finalize the block with the first channel frame on L1. + env.Miner.ActL1SafeNext(t) + env.Miner.ActL1FinalizeNext(t) + } + + env.Batcher.ActCreateChannel(t, testCfg.Custom.useSpanBatch) + + max := func(input []uint) uint { + max := uint(0) + for _, val := range input { + if val > max { + max = val + } + } + return max + } + + if testCfg.Custom.overAdvanceL1Origin > 0 { + // Generate future L1 origin or we cannot advance to it. + env.Miner.ActEmptyBlock(t) + } + + targetHeadNumber := max(testCfg.Custom.blocks) + for env.Engine.L2Chain().CurrentBlock().Number.Uint64() < uint64(targetHeadNumber) { + parentNum := env.Engine.L2Chain().CurrentBlock().Number.Uint64() + + if testCfg.Custom.breachMaxSequencerDrift { + // prevent L1 origin from progressing + env.Sequencer.ActL2KeepL1Origin(t) + } else if oa := testCfg.Custom.overAdvanceL1Origin; oa > 0 && oa == int(parentNum)+1 { + env.Sequencer.ActL2ForceAdvanceL1Origin(t) + } + + env.Sequencer.ActL2StartBlock(t) + + if !testCfg.Custom.breachMaxSequencerDrift { + // Send an L2 tx + env.Alice.L2.ActResetTxOpts(t) + env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob) + env.Alice.L2.ActMakeTx(t) + env.Engine.ActL2IncludeTx(env.Alice.Address())(t) + } + + if testCfg.Custom.breachMaxSequencerDrift && + parentNum == 1799 || + parentNum == 1800 || + parentNum == 1801 { + // Send an L2 tx and force sequencer to include it + env.Alice.L2.ActResetTxOpts(t) + env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob) + env.Alice.L2.ActMakeTx(t) + env.Engine.ActL2IncludeTxIgnoreForcedEmpty(env.Alice.Address())(t) + } + + env.Sequencer.ActL2EndBlock(t) + } + + // Buffer the blocks in the batcher. + for i, blockNum := range testCfg.Custom.blocks { + var blockModifier actionsHelpers.BlockModifier + if len(testCfg.Custom.blockModifiers) > i { + blockModifier = testCfg.Custom.blockModifiers[i] + } + env.Batcher.ActAddBlockByNumber(t, int64(blockNum), blockModifier, actionsHelpers.BlockLogger(t)) + + } + + env.Batcher.ActL2ChannelClose(t) + frame := env.Batcher.ReadNextOutputFrame(t) + require.NotEmpty(t, frame) + env.Batcher.ActL2BatchSubmitRaw(t, frame) + includeBatchTx() + + // Instruct the sequencer to derive the L2 chain from the data on L1 that the batcher just posted. + env.Sequencer.ActL1HeadSignal(t) + env.Sequencer.ActL2PipelineFull(t) + + l2SafeHead := env.Sequencer.L2Safe() + + isHolocene := testCfg.Hardfork.Precedence >= helpers.Holocene.Precedence + testCfg.Custom.RequireExpectedProgressAndLogs(t, l2SafeHead, isHolocene, env.Engine, env.Logs) + t.Log("Safe head progressed as expected", "l2SafeHeadNumber", l2SafeHead.Number) + + if safeHeadNumber := l2SafeHead.Number; safeHeadNumber > 0 { + env.RunFaultProofProgram(t, safeHeadNumber, testCfg.CheckResult, testCfg.InputParams...) + } + } + + matrix := helpers.NewMatrix[testCase]() + defer matrix.Run(gt) + + for _, ordering := range testCases { + matrix.AddTestCase( + fmt.Sprintf("HonestClaim-%s", ordering.name), + ordering, + helpers.NewForkMatrix(helpers.Granite, helpers.LatestFork), + runHoloceneDerivationTest, + helpers.ExpectNoError(), + ) + matrix.AddTestCase( + fmt.Sprintf("JunkClaim-%s", ordering.name), + ordering, + helpers.NewForkMatrix(helpers.Granite, helpers.LatestFork), + runHoloceneDerivationTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), + ) + } +} diff --git a/op-e2e/actions/proofs/l1_lookback_test.go b/op-e2e/actions/proofs/l1_lookback_test.go index b40635ac55097..3e9c6fdcd52b9 100644 --- a/op-e2e/actions/proofs/l1_lookback_test.go +++ b/op-e2e/actions/proofs/l1_lookback_test.go @@ -73,11 +73,12 @@ func runL1LookbackTest_ReopenChannel(gt *testing.T, testCfg *helpers.TestCfg[any env.Miner.ActL1SafeNext(t) // Re-submit the first L2 block frame w/ different transaction data. - err := env.Batcher.Buffer(t, func(block *types.Block) { + err := env.Batcher.Buffer(t, func(block *types.Block) *types.Block { env.Bob.L2.ActResetTxOpts(t) env.Bob.L2.ActSetTxToAddr(&env.Dp.Addresses.Mallory) tx := env.Bob.L2.MakeTransaction(t) block.Transactions()[1] = tx + return block }) require.NoError(t, err) env.Batcher.ActL2BatchSubmit(t) diff --git a/op-e2e/actions/proofs/sequence_window_expiry_test.go b/op-e2e/actions/proofs/sequence_window_expiry_test.go index 3f5ca9562d4bf..cb702fe8eb6a4 100644 --- a/op-e2e/actions/proofs/sequence_window_expiry_test.go +++ b/op-e2e/actions/proofs/sequence_window_expiry_test.go @@ -132,17 +132,18 @@ func Test_ProgramAction_SequenceWindowExpired(gt *testing.T) { matrix := helpers.NewMatrix[any]() defer matrix.Run(gt) + forks := helpers.ForkMatrix{helpers.Granite, helpers.LatestFork} matrix.AddTestCase( "HonestClaim", nil, - helpers.LatestForkOnly, + forks, runSequenceWindowExpireTest, helpers.ExpectNoError(), ) matrix.AddTestCase( "JunkClaim", nil, - helpers.LatestForkOnly, + forks, runSequenceWindowExpireTest, helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), @@ -150,14 +151,14 @@ func Test_ProgramAction_SequenceWindowExpired(gt *testing.T) { matrix.AddTestCase( "ChannelCloseAfterWindowExpiry-HonestClaim", nil, - helpers.LatestForkOnly, + forks, runSequenceWindowExpire_ChannelCloseAfterWindowExpiry_Test, helpers.ExpectNoError(), ) matrix.AddTestCase( "ChannelCloseAfterWindowExpiry-JunkClaim", nil, - helpers.LatestForkOnly, + forks, runSequenceWindowExpire_ChannelCloseAfterWindowExpiry_Test, helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), diff --git a/op-e2e/actions/proofs/trace_extension_test.go b/op-e2e/actions/proofs/trace_extension_test.go new file mode 100644 index 0000000000000..7dac46594d2cf --- /dev/null +++ b/op-e2e/actions/proofs/trace_extension_test.go @@ -0,0 +1,68 @@ +package proofs + +import ( + "testing" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func runSafeHeadTraceExtensionTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { + t := actionsHelpers.NewDefaultTesting(gt) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + // Build an empty block on L2 + env.Sequencer.ActL2StartBlock(t) + env.Sequencer.ActL2EndBlock(t) + + // Instruct the batcher to submit the block to L1, and include the transaction. + env.Batcher.ActSubmitAll(t) + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t) + env.Miner.ActL1EndBlock(t) + + // Instruct the sequencer to derive the L2 chain from the data on L1 that the batcher just posted. + env.Sequencer.ActL1HeadSignal(t) + env.Sequencer.ActL2PipelineFull(t) + + l1Head := env.Miner.L1Chain().CurrentBlock() + l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock() + + // Ensure there is only 1 block on L1. + require.Equal(t, uint64(1), l1Head.Number.Uint64()) + // Ensure the block is marked as safe before we attempt to fault prove it. + require.Equal(t, uint64(1), l2SafeHead.Number.Uint64()) + + // Set claimed L2 block number to be past the actual safe head (still using the safe head output as the claim) + params := []helpers.FixtureInputParam{helpers.WithL2BlockNumber(l2SafeHead.Number.Uint64() + 1)} + params = append(params, testCfg.InputParams...) + env.RunFaultProofProgram(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, params...) +} + +// Test_ProgramAction_SafeHeadTraceExtension checks that op-program correctly handles the trace extension case where +// the claimed l2 block number is after the safe head. The honest actor should repeat the output root from the safe head +// and op-program should consider it valid even though the claimed l2 block number is not reached. +// Output roots other than from the safe head should be invalid if the claimed l2 block number is not reached. +func Test_ProgramAction_SafeHeadTraceExtension(gt *testing.T) { + matrix := helpers.NewMatrix[any]() + defer matrix.Run(gt) + + matrix.AddTestCase( + "HonestClaim", + nil, + helpers.LatestForkOnly, + runSafeHeadTraceExtensionTest, + helpers.ExpectNoError(), + ) + matrix.AddTestCase( + "JunkClaim", + nil, + helpers.LatestForkOnly, + runSafeHeadTraceExtensionTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), + ) +} diff --git a/op-e2e/actions/sync/sync_test.go b/op-e2e/actions/sync/sync_test.go index 95f55d63fcdd3..b9c29404dca56 100644 --- a/op-e2e/actions/sync/sync_test.go +++ b/op-e2e/actions/sync/sync_test.go @@ -618,8 +618,8 @@ func TestBackupUnsafeReorgForkChoiceNotInputError(gt *testing.T) { // check pendingSafe is reset require.Equal(t, sequencer.L2PendingSafe().Number, uint64(0)) // check backupUnsafe is applied - require.Equal(t, sequencer.L2Unsafe().Hash, targetUnsafeHeadHash) - require.Equal(t, sequencer.L2Unsafe().Number, uint64(5)) + require.Equal(t, uint64(5), sequencer.L2Unsafe().Number) + require.Equal(t, targetUnsafeHeadHash, sequencer.L2Unsafe().Hash) // safe head cannot be advanced because batch contained invalid blocks require.Equal(t, sequencer.L2Safe().Number, uint64(0)) } diff --git a/op-e2e/actions/upgrades/dencun_fork_test.go b/op-e2e/actions/upgrades/dencun_fork_test.go index b15634c78adfe..a84e274b591aa 100644 --- a/op-e2e/actions/upgrades/dencun_fork_test.go +++ b/op-e2e/actions/upgrades/dencun_fork_test.go @@ -126,6 +126,7 @@ func TestDencunL2ForkAfterGenesis(gt *testing.T) { dp.DeployConfig.L2GenesisEcotoneTimeOffset = &offset dp.DeployConfig.L2GenesisFjordTimeOffset = nil dp.DeployConfig.L2GenesisGraniteTimeOffset = nil + dp.DeployConfig.L2GenesisHoloceneTimeOffset = nil // New forks have to be added here, after changing the default deploy config! sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc) @@ -189,7 +190,7 @@ func aliceSimpleBlobTx(t helpers.Testing, dp *e2eutils.DeployParams) *types.Tran func newEngine(t helpers.Testing, sd *e2eutils.SetupData, log log.Logger) *helpers.L2Engine { jwtPath := e2eutils.WriteDefaultJWT(t) - return helpers.NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath) + return helpers.NewL2Engine(t, log, sd.L2Cfg, jwtPath) } // TestDencunBlobTxRPC tries to send a Blob tx to the L2 engine via RPC, it should not be accepted. @@ -203,7 +204,7 @@ func TestDencunBlobTxRPC(gt *testing.T) { cl := engine.EthClient() tx := aliceSimpleBlobTx(t, dp) err := cl.SendTransaction(context.Background(), tx) - require.ErrorContains(t, err, "transaction type not supported") + require.NoError(t, err, "must accept blob tx via RPC") } // TestDencunBlobTxInTxPool tries to insert a blob tx directly into the tx pool, it should not be accepted. @@ -216,7 +217,7 @@ func TestDencunBlobTxInTxPool(gt *testing.T) { engine := newEngine(t, sd, log) tx := aliceSimpleBlobTx(t, dp) errs := engine.Eth.TxPool().Add([]*types.Transaction{tx}, true, true) - require.ErrorContains(t, errs[0], "transaction type not supported") + require.NoError(t, errs[0], "must accept blob tx In tx pool") } // TestDencunBlobTxInclusion tries to send a Blob tx to the L2 engine, it should not be accepted. @@ -234,5 +235,5 @@ func TestDencunBlobTxInclusion(gt *testing.T) { sequencer.ActL2StartBlock(t) err := engine.EngineApi.IncludeTx(tx, dp.Addresses.Alice) - require.ErrorContains(t, err, "invalid L2 block (tx 1): failed to apply transaction to L2 block (tx 1): transaction type not supported") + require.NoError(t, err, "must inlcude blob tx") } diff --git a/op-e2e/actions/upgrades/ecotone_fork_test.go b/op-e2e/actions/upgrades/ecotone_fork_test.go index 6b51b5b470a4d..9bbd5426b5d83 100644 --- a/op-e2e/actions/upgrades/ecotone_fork_test.go +++ b/op-e2e/actions/upgrades/ecotone_fork_test.go @@ -52,6 +52,7 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) { dp.DeployConfig.L2GenesisEcotoneTimeOffset = &ecotoneOffset dp.DeployConfig.L2GenesisFjordTimeOffset = nil dp.DeployConfig.L2GenesisGraniteTimeOffset = nil + dp.DeployConfig.L2GenesisHoloceneTimeOffset = nil // New forks have to be added here... require.NoError(t, dp.DeployConfig.Check(log), "must have valid config") diff --git a/op-e2e/actions/upgrades/fjord_fork_test.go b/op-e2e/actions/upgrades/fjord_fork_test.go index 564ee49aa17d6..934aa411299fe 100644 --- a/op-e2e/actions/upgrades/fjord_fork_test.go +++ b/op-e2e/actions/upgrades/fjord_fork_test.go @@ -45,6 +45,7 @@ func TestFjordNetworkUpgradeTransactions(gt *testing.T) { dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock dp.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisBlock dp.DeployConfig.L2GenesisFjordTimeOffset = &fjordOffset + dp.DeployConfig.L2GenesisGraniteTimeOffset = nil require.NoError(t, dp.DeployConfig.Check(log), "must have valid config") sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc) diff --git a/op-e2e/actions/upgrades/helpers/config.go b/op-e2e/actions/upgrades/helpers/config.go index a936d86250a05..c09d0be48b6c2 100644 --- a/op-e2e/actions/upgrades/helpers/config.go +++ b/op-e2e/actions/upgrades/helpers/config.go @@ -16,6 +16,7 @@ func ApplyDeltaTimeOffset(dp *e2eutils.DeployParams, deltaTimeOffset *hexutil.Ui dp.DeployConfig.L2GenesisEcotoneTimeOffset = deltaTimeOffset } } + // configure Fjord to not be before Delta accidentally if dp.DeployConfig.L2GenesisFjordTimeOffset != nil { if deltaTimeOffset == nil { @@ -24,4 +25,22 @@ func ApplyDeltaTimeOffset(dp *e2eutils.DeployParams, deltaTimeOffset *hexutil.Ui dp.DeployConfig.L2GenesisFjordTimeOffset = deltaTimeOffset } } + + // configure Granite to not be before Delta accidentally + if dp.DeployConfig.L2GenesisGraniteTimeOffset != nil { + if deltaTimeOffset == nil { + dp.DeployConfig.L2GenesisGraniteTimeOffset = nil + } else if *dp.DeployConfig.L2GenesisGraniteTimeOffset < *deltaTimeOffset { + dp.DeployConfig.L2GenesisGraniteTimeOffset = deltaTimeOffset + } + } + + // configure Holocene to not be before Delta accidentally + if dp.DeployConfig.L2GenesisHoloceneTimeOffset != nil { + if deltaTimeOffset == nil { + dp.DeployConfig.L2GenesisHoloceneTimeOffset = nil + } else if *dp.DeployConfig.L2GenesisHoloceneTimeOffset < *deltaTimeOffset { + dp.DeployConfig.L2GenesisHoloceneTimeOffset = deltaTimeOffset + } + } } diff --git a/op-e2e/actions/upgrades/holocene_fork_test.go b/op-e2e/actions/upgrades/holocene_fork_test.go new file mode 100644 index 0000000000000..12c0dcde6d194 --- /dev/null +++ b/op-e2e/actions/upgrades/holocene_fork_test.go @@ -0,0 +1,216 @@ +package upgrades + +import ( + "context" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/core/types" + + "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestHoloceneActivationAtGenesis(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + env := helpers.SetupEnv(t, helpers.WithActiveGenesisFork(rollup.Holocene)) + + // Start op-nodes + env.Seq.ActL2PipelineFull(t) + env.Verifier.ActL2PipelineFull(t) + + // Verify Holocene is active at genesis + l2Head := env.Seq.L2Unsafe() + require.NotZero(t, l2Head.Hash) + require.True(t, env.SetupData.RollupCfg.IsHolocene(l2Head.Time), "Holocene should be active at genesis") + + // build empty L1 block + env.Miner.ActEmptyBlock(t) + + // Build L2 chain and advance safe head + env.Seq.ActL1HeadSignal(t) + env.Seq.ActBuildToL1Head(t) + + // verify in logs that correct stage got activated + recs := env.Logs.FindLogs(testlog.NewMessageContainsFilter("activating Holocene stage during reset"), testlog.NewAttributesFilter("role", e2esys.RoleSeq)) + require.Len(t, recs, 2) + recs = env.Logs.FindLogs(testlog.NewMessageContainsFilter("activating Holocene stage during reset"), testlog.NewAttributesFilter("role", e2esys.RoleVerif)) + require.Len(t, recs, 2) + + env.ActBatchSubmitAllAndMine(t) + + // verifier picks up the L2 chain that was submitted + env.Verifier.ActL1HeadSignal(t) + env.Verifier.ActL2PipelineFull(t) + require.Equal(t, env.Verifier.L2Safe(), env.Seq.L2Unsafe(), "verifier syncs from sequencer via L1") + require.NotEqual(t, env.Seq.L2Safe(), env.Seq.L2Unsafe(), "sequencer has not processed L1 yet") +} + +func TestHoloceneLateActivationAndReset(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + holoceneOffset := uint64(24) + env := helpers.SetupEnv(t, helpers.WithActiveFork(rollup.Holocene, holoceneOffset)) + + requireHoloceneTransformationLogs := func(role string, expNumLogs int) { + recs := env.Logs.FindLogs(testlog.NewMessageContainsFilter("transforming to Holocene"), testlog.NewAttributesFilter("role", role)) + require.Len(t, recs, expNumLogs) + if expNumLogs > 0 { + fqRecs := env.Logs.FindLogs(testlog.NewMessageFilter("FrameQueue: resetting with Holocene activation"), testlog.NewAttributesFilter("role", role)) + require.Len(t, fqRecs, 1) + } + } + + requirePreHoloceneActivationLogs := func(role string, expNumLogs int) { + recs := env.Logs.FindLogs(testlog.NewMessageContainsFilter("activating pre-Holocene stage during reset"), testlog.NewAttributesFilter("role", role)) + require.Len(t, recs, expNumLogs) + } + + // Start op-nodes + env.Seq.ActL2PipelineFull(t) + env.Verifier.ActL2PipelineFull(t) + + // Verify Holocene is not active at genesis yet + l2Head := env.Seq.L2Unsafe() + require.NotZero(t, l2Head.Hash) + require.True(t, env.SetupData.RollupCfg.IsGranite(l2Head.Time), "Granite should be active at genesis") + require.False(t, env.SetupData.RollupCfg.IsHolocene(l2Head.Time), "Holocene should not be active at genesis") + + requirePreHoloceneActivationLogs(e2esys.RoleSeq, 2) + requirePreHoloceneActivationLogs(e2esys.RoleVerif, 2) + // Verify no stage transformations took place yet + requireHoloceneTransformationLogs(e2esys.RoleSeq, 0) + requireHoloceneTransformationLogs(e2esys.RoleVerif, 0) + + env.Seq.ActL2EmptyBlock(t) + l1PreHolocene := env.ActBatchSubmitAllAndMine(t) + require.False(t, env.SetupData.RollupCfg.IsHolocene(l1PreHolocene.Time()), + "Holocene should not be active at the first L1 inclusion block") + + // Build a few L2 blocks. We only need the L1 inclusion to advance past Holocene and Holocene + // shouldn't activate with L2 time. + env.Seq.ActBuildL2ToHolocene(t) + + // verify in logs that stage transformations hasn't happened yet, activates by L1 inclusion block + requireHoloceneTransformationLogs(e2esys.RoleSeq, 0) + requireHoloceneTransformationLogs(e2esys.RoleVerif, 0) + + // Submit L2 + l1Head := env.ActBatchSubmitAllAndMine(t) + require.True(t, env.SetupData.RollupCfg.IsHolocene(l1Head.Time())) + + // verifier picks up the L2 chain that was submitted + env.Verifier.ActL1HeadSignal(t) + env.Verifier.ActL2PipelineFull(t) + l2Safe := env.Verifier.L2Safe() + require.Equal(t, l2Safe, env.Seq.L2Unsafe(), "verifier syncs from sequencer via L1") + require.NotEqual(t, env.Seq.L2Safe(), env.Seq.L2Unsafe(), "sequencer has not processed L1 yet") + require.True(t, env.SetupData.RollupCfg.IsHolocene(l2Safe.Time), "Holocene should now be active") + requireHoloceneTransformationLogs(e2esys.RoleSeq, 0) + requireHoloceneTransformationLogs(e2esys.RoleVerif, 2) + + // sequencer also picks up L2 safe chain + env.Seq.ActL1HeadSignal(t) + env.Seq.ActL2PipelineFull(t) + requireHoloceneTransformationLogs(e2esys.RoleSeq, 2) + require.Equal(t, env.Seq.L2Safe(), env.Seq.L2Unsafe(), "sequencer has processed L1") + + // reorg L1 without batch submission + env.Miner.ActL1RewindToParent(t) + env.Miner.ActEmptyBlock(t) + env.Miner.ActEmptyBlock(t) + + env.Seq.ActL1HeadSignal(t) + env.Verifier.ActL1HeadSignal(t) + env.Seq.ActL2PipelineFull(t) + env.Verifier.ActL2PipelineFull(t) + + // duplicate activation logs + requirePreHoloceneActivationLogs(e2esys.RoleSeq, 4) + requirePreHoloceneActivationLogs(e2esys.RoleVerif, 4) +} + +func TestHoloceneInvalidPayload(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + env := helpers.SetupEnv(t, helpers.WithActiveGenesisFork(rollup.Holocene)) + ctx := context.Background() + + requireDepositOnlyLogs := func(role string, expNumLogs int) { + t.Helper() + recs := env.Logs.FindLogs(testlog.NewMessageContainsFilter("deposits-only attributes"), testlog.NewAttributesFilter("role", role)) + require.Len(t, recs, expNumLogs) + } + + // Start op-nodes + env.Seq.ActL2PipelineFull(t) + + // generate and batch buffer two empty blocks + env.Seq.ActL2EmptyBlock(t) // 1 - genesis is 0 + env.Batcher.ActL2BatchBuffer(t) + env.Seq.ActL2EmptyBlock(t) // 2 + env.Batcher.ActL2BatchBuffer(t) + + // send and include a single transaction + env.Alice.L2.ActResetTxOpts(t) + env.Alice.L2.ActSetTxToAddr(&env.DeployParams.Addresses.Bob) + env.Alice.L2.ActMakeTx(t) + + env.Seq.ActL2StartBlock(t) + env.SeqEngine.ActL2IncludeTx(env.Alice.Address())(t) + env.Seq.ActL2EndBlock(t) // 3 + env.Alice.L2.ActCheckReceiptStatusOfLastTx(true)(t) + l2Unsafe := env.Seq.L2Unsafe() + const invalidNum = 3 + require.EqualValues(t, invalidNum, l2Unsafe.Number) + b, err := env.SeqEngine.EthClient().BlockByNumber(ctx, big.NewInt(invalidNum)) + require.NoError(t, err) + require.Len(t, b.Transactions(), 2) + + // buffer into the batcher, invalidating the tx via signature zeroing + env.Batcher.ActL2BatchBuffer(t, func(block *types.Block) *types.Block { + // Replace the tx with one that has a bad signature. + txs := block.Transactions() + newTx, err := txs[1].WithSignature(env.Alice.L2.Signer(), make([]byte, 65)) + require.NoError(t, err) + txs[1] = newTx + return block + }) + + // generate two more empty blocks + env.Seq.ActL2EmptyBlock(t) // 4 + env.Seq.ActL2EmptyBlock(t) // 5 + require.EqualValues(t, 5, env.Seq.L2Unsafe().Number) + + // submit it all + env.ActBatchSubmitAllAndMine(t) + + // derive chain on sequencer + env.Seq.ActL1HeadSignal(t) + env.Seq.ActL2PipelineFull(t) + + l2Safe := env.Seq.L2Safe() + require.EqualValues(t, invalidNum, l2Safe.Number) + require.NotEqual(t, l2Safe.Hash, l2Unsafe.Hash, // old L2Unsafe above + "block-3 should have been replaced by deposit-only version") + requireDepositOnlyLogs(e2esys.RoleSeq, 2) + require.Equal(t, l2Safe, env.Seq.L2Unsafe(), "unsafe chain should have reorg'd") + b, err = env.SeqEngine.EthClient().BlockByNumber(ctx, big.NewInt(invalidNum)) + require.NoError(t, err) + require.Len(t, b.Transactions(), 1) + + // test that building on top of reorg'd chain and deriving further works + + env.Seq.ActL2EmptyBlock(t) // 4 + env.Seq.ActL2EmptyBlock(t) // 5 + l2Unsafe = env.Seq.L2Unsafe() + require.EqualValues(t, 5, l2Unsafe.Number) + + env.Batcher.Reset() // need to reset batcher to become aware of reorg + env.ActBatchSubmitAllAndMine(t) + env.Seq.ActL1HeadSignal(t) + env.Seq.ActL2PipelineFull(t) + require.Equal(t, l2Unsafe, env.Seq.L2Safe()) +} diff --git a/op-e2e/actions/upgrades/span_batch_test.go b/op-e2e/actions/upgrades/span_batch_test.go index 3888cae8a5e07..0588128b7dbb0 100644 --- a/op-e2e/actions/upgrades/span_batch_test.go +++ b/op-e2e/actions/upgrades/span_batch_test.go @@ -142,6 +142,7 @@ func TestHardforkMiddleOfSpanBatch(gt *testing.T) { dp.DeployConfig.L2GenesisEcotoneTimeOffset = nil dp.DeployConfig.L2GenesisFjordTimeOffset = nil dp.DeployConfig.L2GenesisGraniteTimeOffset = nil + dp.DeployConfig.L2GenesisHoloceneTimeOffset = nil sd := e2eutils.Setup(t, dp, actionsHelpers.DefaultAlloc) log := testlog.Logger(t, log.LevelError) diff --git a/op-e2e/bindings/delayedvetoable.go b/op-e2e/bindings/delayedvetoable.go deleted file mode 100644 index 989bb0278d205..0000000000000 --- a/op-e2e/bindings/delayedvetoable.go +++ /dev/null @@ -1,928 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package bindings - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription -) - -// DelayedVetoableMetaData contains all meta data concerning the DelayedVetoable contract. -var DelayedVetoableMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"vetoer_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"initiator_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"target_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatingDelay_\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"fallback\",\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"delay\",\"inputs\":[],\"outputs\":[{\"name\":\"delay_\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initiator\",\"inputs\":[],\"outputs\":[{\"name\":\"initiator_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"queuedAt\",\"inputs\":[{\"name\":\"callHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"queuedAt_\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"target\",\"inputs\":[],\"outputs\":[{\"name\":\"target_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"vetoer\",\"inputs\":[],\"outputs\":[{\"name\":\"vetoer_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"DelayActivated\",\"inputs\":[{\"name\":\"delay\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Forwarded\",\"inputs\":[{\"name\":\"callHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"data\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initiated\",\"inputs\":[{\"name\":\"callHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"data\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Vetoed\",\"inputs\":[{\"name\":\"callHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"data\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"ForwardingEarly\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"Unauthorized\",\"inputs\":[{\"name\":\"expected\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"actual\",\"type\":\"address\",\"internalType\":\"address\"}]}]", - Bin: "0x61010060405234801561001157600080fd5b506040516108ff3803806108ff8339810160408190526100309161006e565b6001600160a01b0393841660a05291831660c05290911660805260e0526100b9565b80516001600160a01b038116811461006957600080fd5b919050565b6000806000806080858703121561008457600080fd5b61008d85610052565b935061009b60208601610052565b92506100a960408601610052565b6060959095015193969295505050565b60805160a05160c05160e0516107dc610123600039600061023f01526000818161015f01528181610205015281816102cd0152818161045801526105050152600081816101a001528181610384015261059d01526000818161057101526105ff01526107dc6000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c8063b912de5d11610050578063b912de5d14610111578063d4b8399214610124578063d8bff4401461012c57610072565b806354fd4d501461007c5780635c39fcc1146100ce5780636a42b8f8146100fb575b61007a610134565b005b6100b86040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100c591906106a7565b60405180910390f35b6100d66104fb565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100c5565b610103610532565b6040519081526020016100c5565b61010361011f36600461071a565b610540565b6100d6610567565b6100d6610593565b361580156101425750600054155b15610298573373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016148015906101c357503373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614155b1561023d576040517f295a81c100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660048201523360248201526044015b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000060008190556040519081527febf28bfb587e28dfffd9173cf71c32ba5d3f0544a0117b5539c9b274a5bba2a89060200160405180910390a1565b600080366040516102aa929190610733565b60405190819003902090503373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161480156103065750600081815260016020526040902054155b1561036c5760005460000361031e5761031e816105bf565b6000818152600160205260408082204290555182917f87a332a414acbc7da074543639ce7ae02ff1ea72e88379da9f261b080beb5a139161036191903690610743565b60405180910390a250565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161480156103be575060008181526001602052604090205415155b15610406576000818152600160205260408082208290555182917fbede6852c1d97d93ff557f676de76670cd0dec861e7fe8beb13aa0ba2b0ab0409161036191903690610743565b600081815260016020526040812054900361048b576040517f295a81c100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166004820152336024820152604401610234565b60008054828252600160205260409091205442916104a891610790565b11156104e0576040517f43dc986d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152600160205260408120556104f8816105bf565b50565b60003361052757507f000000000000000000000000000000000000000000000000000000000000000090565b61052f610134565b90565b600033610527575060005490565b60003361055a575060009081526001602052604090205490565b610562610134565b919050565b60003361052757507f000000000000000000000000000000000000000000000000000000000000000090565b60003361052757507f000000000000000000000000000000000000000000000000000000000000000090565b807f4c109d85bcd0bb5c735b4be850953d652afe4cd9aa2e0b1426a65a4dcb2e12296000366040516105f2929190610743565b60405180910390a26000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16600036604051610645929190610733565b6000604051808303816000865af19150503d8060008114610682576040519150601f19603f3d011682016040523d82523d6000602084013e610687565b606091505b50909250905081151560010361069f57805160208201f35b805160208201fd5b600060208083528351808285015260005b818110156106d4578581018301518582016040015282016106b8565b818111156106e6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561072c57600080fd5b5035919050565b8183823760009101908152919050565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b600082198211156107ca577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50019056fea164736f6c634300080f000a", -} - -// DelayedVetoableABI is the input ABI used to generate the binding from. -// Deprecated: Use DelayedVetoableMetaData.ABI instead. -var DelayedVetoableABI = DelayedVetoableMetaData.ABI - -// DelayedVetoableBin is the compiled bytecode used for deploying new contracts. -// Deprecated: Use DelayedVetoableMetaData.Bin instead. -var DelayedVetoableBin = DelayedVetoableMetaData.Bin - -// DeployDelayedVetoable deploys a new Ethereum contract, binding an instance of DelayedVetoable to it. -func DeployDelayedVetoable(auth *bind.TransactOpts, backend bind.ContractBackend, vetoer_ common.Address, initiator_ common.Address, target_ common.Address, operatingDelay_ *big.Int) (common.Address, *types.Transaction, *DelayedVetoable, error) { - parsed, err := DelayedVetoableMetaData.GetAbi() - if err != nil { - return common.Address{}, nil, nil, err - } - if parsed == nil { - return common.Address{}, nil, nil, errors.New("GetABI returned nil") - } - - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(DelayedVetoableBin), backend, vetoer_, initiator_, target_, operatingDelay_) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &DelayedVetoable{DelayedVetoableCaller: DelayedVetoableCaller{contract: contract}, DelayedVetoableTransactor: DelayedVetoableTransactor{contract: contract}, DelayedVetoableFilterer: DelayedVetoableFilterer{contract: contract}}, nil -} - -// DelayedVetoable is an auto generated Go binding around an Ethereum contract. -type DelayedVetoable struct { - DelayedVetoableCaller // Read-only binding to the contract - DelayedVetoableTransactor // Write-only binding to the contract - DelayedVetoableFilterer // Log filterer for contract events -} - -// DelayedVetoableCaller is an auto generated read-only Go binding around an Ethereum contract. -type DelayedVetoableCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// DelayedVetoableTransactor is an auto generated write-only Go binding around an Ethereum contract. -type DelayedVetoableTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// DelayedVetoableFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type DelayedVetoableFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// DelayedVetoableSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type DelayedVetoableSession struct { - Contract *DelayedVetoable // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// DelayedVetoableCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type DelayedVetoableCallerSession struct { - Contract *DelayedVetoableCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// DelayedVetoableTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type DelayedVetoableTransactorSession struct { - Contract *DelayedVetoableTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// DelayedVetoableRaw is an auto generated low-level Go binding around an Ethereum contract. -type DelayedVetoableRaw struct { - Contract *DelayedVetoable // Generic contract binding to access the raw methods on -} - -// DelayedVetoableCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type DelayedVetoableCallerRaw struct { - Contract *DelayedVetoableCaller // Generic read-only contract binding to access the raw methods on -} - -// DelayedVetoableTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type DelayedVetoableTransactorRaw struct { - Contract *DelayedVetoableTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewDelayedVetoable creates a new instance of DelayedVetoable, bound to a specific deployed contract. -func NewDelayedVetoable(address common.Address, backend bind.ContractBackend) (*DelayedVetoable, error) { - contract, err := bindDelayedVetoable(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &DelayedVetoable{DelayedVetoableCaller: DelayedVetoableCaller{contract: contract}, DelayedVetoableTransactor: DelayedVetoableTransactor{contract: contract}, DelayedVetoableFilterer: DelayedVetoableFilterer{contract: contract}}, nil -} - -// NewDelayedVetoableCaller creates a new read-only instance of DelayedVetoable, bound to a specific deployed contract. -func NewDelayedVetoableCaller(address common.Address, caller bind.ContractCaller) (*DelayedVetoableCaller, error) { - contract, err := bindDelayedVetoable(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &DelayedVetoableCaller{contract: contract}, nil -} - -// NewDelayedVetoableTransactor creates a new write-only instance of DelayedVetoable, bound to a specific deployed contract. -func NewDelayedVetoableTransactor(address common.Address, transactor bind.ContractTransactor) (*DelayedVetoableTransactor, error) { - contract, err := bindDelayedVetoable(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &DelayedVetoableTransactor{contract: contract}, nil -} - -// NewDelayedVetoableFilterer creates a new log filterer instance of DelayedVetoable, bound to a specific deployed contract. -func NewDelayedVetoableFilterer(address common.Address, filterer bind.ContractFilterer) (*DelayedVetoableFilterer, error) { - contract, err := bindDelayedVetoable(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &DelayedVetoableFilterer{contract: contract}, nil -} - -// bindDelayedVetoable binds a generic wrapper to an already deployed contract. -func bindDelayedVetoable(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(DelayedVetoableABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_DelayedVetoable *DelayedVetoableRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _DelayedVetoable.Contract.DelayedVetoableCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_DelayedVetoable *DelayedVetoableRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _DelayedVetoable.Contract.DelayedVetoableTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_DelayedVetoable *DelayedVetoableRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _DelayedVetoable.Contract.DelayedVetoableTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_DelayedVetoable *DelayedVetoableCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _DelayedVetoable.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_DelayedVetoable *DelayedVetoableTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _DelayedVetoable.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_DelayedVetoable *DelayedVetoableTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _DelayedVetoable.Contract.contract.Transact(opts, method, params...) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_DelayedVetoable *DelayedVetoableCaller) Version(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _DelayedVetoable.contract.Call(opts, &out, "version") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_DelayedVetoable *DelayedVetoableSession) Version() (string, error) { - return _DelayedVetoable.Contract.Version(&_DelayedVetoable.CallOpts) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_DelayedVetoable *DelayedVetoableCallerSession) Version() (string, error) { - return _DelayedVetoable.Contract.Version(&_DelayedVetoable.CallOpts) -} - -// Delay is a paid mutator transaction binding the contract method 0x6a42b8f8. -// -// Solidity: function delay() returns(uint256 delay_) -func (_DelayedVetoable *DelayedVetoableTransactor) Delay(opts *bind.TransactOpts) (*types.Transaction, error) { - return _DelayedVetoable.contract.Transact(opts, "delay") -} - -// Delay is a paid mutator transaction binding the contract method 0x6a42b8f8. -// -// Solidity: function delay() returns(uint256 delay_) -func (_DelayedVetoable *DelayedVetoableSession) Delay() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Delay(&_DelayedVetoable.TransactOpts) -} - -// Delay is a paid mutator transaction binding the contract method 0x6a42b8f8. -// -// Solidity: function delay() returns(uint256 delay_) -func (_DelayedVetoable *DelayedVetoableTransactorSession) Delay() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Delay(&_DelayedVetoable.TransactOpts) -} - -// Initiator is a paid mutator transaction binding the contract method 0x5c39fcc1. -// -// Solidity: function initiator() returns(address initiator_) -func (_DelayedVetoable *DelayedVetoableTransactor) Initiator(opts *bind.TransactOpts) (*types.Transaction, error) { - return _DelayedVetoable.contract.Transact(opts, "initiator") -} - -// Initiator is a paid mutator transaction binding the contract method 0x5c39fcc1. -// -// Solidity: function initiator() returns(address initiator_) -func (_DelayedVetoable *DelayedVetoableSession) Initiator() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Initiator(&_DelayedVetoable.TransactOpts) -} - -// Initiator is a paid mutator transaction binding the contract method 0x5c39fcc1. -// -// Solidity: function initiator() returns(address initiator_) -func (_DelayedVetoable *DelayedVetoableTransactorSession) Initiator() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Initiator(&_DelayedVetoable.TransactOpts) -} - -// QueuedAt is a paid mutator transaction binding the contract method 0xb912de5d. -// -// Solidity: function queuedAt(bytes32 callHash) returns(uint256 queuedAt_) -func (_DelayedVetoable *DelayedVetoableTransactor) QueuedAt(opts *bind.TransactOpts, callHash [32]byte) (*types.Transaction, error) { - return _DelayedVetoable.contract.Transact(opts, "queuedAt", callHash) -} - -// QueuedAt is a paid mutator transaction binding the contract method 0xb912de5d. -// -// Solidity: function queuedAt(bytes32 callHash) returns(uint256 queuedAt_) -func (_DelayedVetoable *DelayedVetoableSession) QueuedAt(callHash [32]byte) (*types.Transaction, error) { - return _DelayedVetoable.Contract.QueuedAt(&_DelayedVetoable.TransactOpts, callHash) -} - -// QueuedAt is a paid mutator transaction binding the contract method 0xb912de5d. -// -// Solidity: function queuedAt(bytes32 callHash) returns(uint256 queuedAt_) -func (_DelayedVetoable *DelayedVetoableTransactorSession) QueuedAt(callHash [32]byte) (*types.Transaction, error) { - return _DelayedVetoable.Contract.QueuedAt(&_DelayedVetoable.TransactOpts, callHash) -} - -// Target is a paid mutator transaction binding the contract method 0xd4b83992. -// -// Solidity: function target() returns(address target_) -func (_DelayedVetoable *DelayedVetoableTransactor) Target(opts *bind.TransactOpts) (*types.Transaction, error) { - return _DelayedVetoable.contract.Transact(opts, "target") -} - -// Target is a paid mutator transaction binding the contract method 0xd4b83992. -// -// Solidity: function target() returns(address target_) -func (_DelayedVetoable *DelayedVetoableSession) Target() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Target(&_DelayedVetoable.TransactOpts) -} - -// Target is a paid mutator transaction binding the contract method 0xd4b83992. -// -// Solidity: function target() returns(address target_) -func (_DelayedVetoable *DelayedVetoableTransactorSession) Target() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Target(&_DelayedVetoable.TransactOpts) -} - -// Vetoer is a paid mutator transaction binding the contract method 0xd8bff440. -// -// Solidity: function vetoer() returns(address vetoer_) -func (_DelayedVetoable *DelayedVetoableTransactor) Vetoer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _DelayedVetoable.contract.Transact(opts, "vetoer") -} - -// Vetoer is a paid mutator transaction binding the contract method 0xd8bff440. -// -// Solidity: function vetoer() returns(address vetoer_) -func (_DelayedVetoable *DelayedVetoableSession) Vetoer() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Vetoer(&_DelayedVetoable.TransactOpts) -} - -// Vetoer is a paid mutator transaction binding the contract method 0xd8bff440. -// -// Solidity: function vetoer() returns(address vetoer_) -func (_DelayedVetoable *DelayedVetoableTransactorSession) Vetoer() (*types.Transaction, error) { - return _DelayedVetoable.Contract.Vetoer(&_DelayedVetoable.TransactOpts) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() returns() -func (_DelayedVetoable *DelayedVetoableTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { - return _DelayedVetoable.contract.RawTransact(opts, calldata) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() returns() -func (_DelayedVetoable *DelayedVetoableSession) Fallback(calldata []byte) (*types.Transaction, error) { - return _DelayedVetoable.Contract.Fallback(&_DelayedVetoable.TransactOpts, calldata) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() returns() -func (_DelayedVetoable *DelayedVetoableTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { - return _DelayedVetoable.Contract.Fallback(&_DelayedVetoable.TransactOpts, calldata) -} - -// DelayedVetoableDelayActivatedIterator is returned from FilterDelayActivated and is used to iterate over the raw logs and unpacked data for DelayActivated events raised by the DelayedVetoable contract. -type DelayedVetoableDelayActivatedIterator struct { - Event *DelayedVetoableDelayActivated // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *DelayedVetoableDelayActivatedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableDelayActivated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableDelayActivated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *DelayedVetoableDelayActivatedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *DelayedVetoableDelayActivatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// DelayedVetoableDelayActivated represents a DelayActivated event raised by the DelayedVetoable contract. -type DelayedVetoableDelayActivated struct { - Delay *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterDelayActivated is a free log retrieval operation binding the contract event 0xebf28bfb587e28dfffd9173cf71c32ba5d3f0544a0117b5539c9b274a5bba2a8. -// -// Solidity: event DelayActivated(uint256 delay) -func (_DelayedVetoable *DelayedVetoableFilterer) FilterDelayActivated(opts *bind.FilterOpts) (*DelayedVetoableDelayActivatedIterator, error) { - - logs, sub, err := _DelayedVetoable.contract.FilterLogs(opts, "DelayActivated") - if err != nil { - return nil, err - } - return &DelayedVetoableDelayActivatedIterator{contract: _DelayedVetoable.contract, event: "DelayActivated", logs: logs, sub: sub}, nil -} - -// WatchDelayActivated is a free log subscription operation binding the contract event 0xebf28bfb587e28dfffd9173cf71c32ba5d3f0544a0117b5539c9b274a5bba2a8. -// -// Solidity: event DelayActivated(uint256 delay) -func (_DelayedVetoable *DelayedVetoableFilterer) WatchDelayActivated(opts *bind.WatchOpts, sink chan<- *DelayedVetoableDelayActivated) (event.Subscription, error) { - - logs, sub, err := _DelayedVetoable.contract.WatchLogs(opts, "DelayActivated") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(DelayedVetoableDelayActivated) - if err := _DelayedVetoable.contract.UnpackLog(event, "DelayActivated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseDelayActivated is a log parse operation binding the contract event 0xebf28bfb587e28dfffd9173cf71c32ba5d3f0544a0117b5539c9b274a5bba2a8. -// -// Solidity: event DelayActivated(uint256 delay) -func (_DelayedVetoable *DelayedVetoableFilterer) ParseDelayActivated(log types.Log) (*DelayedVetoableDelayActivated, error) { - event := new(DelayedVetoableDelayActivated) - if err := _DelayedVetoable.contract.UnpackLog(event, "DelayActivated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// DelayedVetoableForwardedIterator is returned from FilterForwarded and is used to iterate over the raw logs and unpacked data for Forwarded events raised by the DelayedVetoable contract. -type DelayedVetoableForwardedIterator struct { - Event *DelayedVetoableForwarded // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *DelayedVetoableForwardedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableForwarded) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableForwarded) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *DelayedVetoableForwardedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *DelayedVetoableForwardedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// DelayedVetoableForwarded represents a Forwarded event raised by the DelayedVetoable contract. -type DelayedVetoableForwarded struct { - CallHash [32]byte - Data []byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterForwarded is a free log retrieval operation binding the contract event 0x4c109d85bcd0bb5c735b4be850953d652afe4cd9aa2e0b1426a65a4dcb2e1229. -// -// Solidity: event Forwarded(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) FilterForwarded(opts *bind.FilterOpts, callHash [][32]byte) (*DelayedVetoableForwardedIterator, error) { - - var callHashRule []interface{} - for _, callHashItem := range callHash { - callHashRule = append(callHashRule, callHashItem) - } - - logs, sub, err := _DelayedVetoable.contract.FilterLogs(opts, "Forwarded", callHashRule) - if err != nil { - return nil, err - } - return &DelayedVetoableForwardedIterator{contract: _DelayedVetoable.contract, event: "Forwarded", logs: logs, sub: sub}, nil -} - -// WatchForwarded is a free log subscription operation binding the contract event 0x4c109d85bcd0bb5c735b4be850953d652afe4cd9aa2e0b1426a65a4dcb2e1229. -// -// Solidity: event Forwarded(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) WatchForwarded(opts *bind.WatchOpts, sink chan<- *DelayedVetoableForwarded, callHash [][32]byte) (event.Subscription, error) { - - var callHashRule []interface{} - for _, callHashItem := range callHash { - callHashRule = append(callHashRule, callHashItem) - } - - logs, sub, err := _DelayedVetoable.contract.WatchLogs(opts, "Forwarded", callHashRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(DelayedVetoableForwarded) - if err := _DelayedVetoable.contract.UnpackLog(event, "Forwarded", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseForwarded is a log parse operation binding the contract event 0x4c109d85bcd0bb5c735b4be850953d652afe4cd9aa2e0b1426a65a4dcb2e1229. -// -// Solidity: event Forwarded(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) ParseForwarded(log types.Log) (*DelayedVetoableForwarded, error) { - event := new(DelayedVetoableForwarded) - if err := _DelayedVetoable.contract.UnpackLog(event, "Forwarded", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// DelayedVetoableInitiatedIterator is returned from FilterInitiated and is used to iterate over the raw logs and unpacked data for Initiated events raised by the DelayedVetoable contract. -type DelayedVetoableInitiatedIterator struct { - Event *DelayedVetoableInitiated // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *DelayedVetoableInitiatedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableInitiated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableInitiated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *DelayedVetoableInitiatedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *DelayedVetoableInitiatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// DelayedVetoableInitiated represents a Initiated event raised by the DelayedVetoable contract. -type DelayedVetoableInitiated struct { - CallHash [32]byte - Data []byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterInitiated is a free log retrieval operation binding the contract event 0x87a332a414acbc7da074543639ce7ae02ff1ea72e88379da9f261b080beb5a13. -// -// Solidity: event Initiated(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) FilterInitiated(opts *bind.FilterOpts, callHash [][32]byte) (*DelayedVetoableInitiatedIterator, error) { - - var callHashRule []interface{} - for _, callHashItem := range callHash { - callHashRule = append(callHashRule, callHashItem) - } - - logs, sub, err := _DelayedVetoable.contract.FilterLogs(opts, "Initiated", callHashRule) - if err != nil { - return nil, err - } - return &DelayedVetoableInitiatedIterator{contract: _DelayedVetoable.contract, event: "Initiated", logs: logs, sub: sub}, nil -} - -// WatchInitiated is a free log subscription operation binding the contract event 0x87a332a414acbc7da074543639ce7ae02ff1ea72e88379da9f261b080beb5a13. -// -// Solidity: event Initiated(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) WatchInitiated(opts *bind.WatchOpts, sink chan<- *DelayedVetoableInitiated, callHash [][32]byte) (event.Subscription, error) { - - var callHashRule []interface{} - for _, callHashItem := range callHash { - callHashRule = append(callHashRule, callHashItem) - } - - logs, sub, err := _DelayedVetoable.contract.WatchLogs(opts, "Initiated", callHashRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(DelayedVetoableInitiated) - if err := _DelayedVetoable.contract.UnpackLog(event, "Initiated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseInitiated is a log parse operation binding the contract event 0x87a332a414acbc7da074543639ce7ae02ff1ea72e88379da9f261b080beb5a13. -// -// Solidity: event Initiated(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) ParseInitiated(log types.Log) (*DelayedVetoableInitiated, error) { - event := new(DelayedVetoableInitiated) - if err := _DelayedVetoable.contract.UnpackLog(event, "Initiated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// DelayedVetoableVetoedIterator is returned from FilterVetoed and is used to iterate over the raw logs and unpacked data for Vetoed events raised by the DelayedVetoable contract. -type DelayedVetoableVetoedIterator struct { - Event *DelayedVetoableVetoed // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *DelayedVetoableVetoedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableVetoed) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(DelayedVetoableVetoed) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *DelayedVetoableVetoedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *DelayedVetoableVetoedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// DelayedVetoableVetoed represents a Vetoed event raised by the DelayedVetoable contract. -type DelayedVetoableVetoed struct { - CallHash [32]byte - Data []byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterVetoed is a free log retrieval operation binding the contract event 0xbede6852c1d97d93ff557f676de76670cd0dec861e7fe8beb13aa0ba2b0ab040. -// -// Solidity: event Vetoed(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) FilterVetoed(opts *bind.FilterOpts, callHash [][32]byte) (*DelayedVetoableVetoedIterator, error) { - - var callHashRule []interface{} - for _, callHashItem := range callHash { - callHashRule = append(callHashRule, callHashItem) - } - - logs, sub, err := _DelayedVetoable.contract.FilterLogs(opts, "Vetoed", callHashRule) - if err != nil { - return nil, err - } - return &DelayedVetoableVetoedIterator{contract: _DelayedVetoable.contract, event: "Vetoed", logs: logs, sub: sub}, nil -} - -// WatchVetoed is a free log subscription operation binding the contract event 0xbede6852c1d97d93ff557f676de76670cd0dec861e7fe8beb13aa0ba2b0ab040. -// -// Solidity: event Vetoed(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) WatchVetoed(opts *bind.WatchOpts, sink chan<- *DelayedVetoableVetoed, callHash [][32]byte) (event.Subscription, error) { - - var callHashRule []interface{} - for _, callHashItem := range callHash { - callHashRule = append(callHashRule, callHashItem) - } - - logs, sub, err := _DelayedVetoable.contract.WatchLogs(opts, "Vetoed", callHashRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(DelayedVetoableVetoed) - if err := _DelayedVetoable.contract.UnpackLog(event, "Vetoed", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseVetoed is a log parse operation binding the contract event 0xbede6852c1d97d93ff557f676de76670cd0dec861e7fe8beb13aa0ba2b0ab040. -// -// Solidity: event Vetoed(bytes32 indexed callHash, bytes data) -func (_DelayedVetoable *DelayedVetoableFilterer) ParseVetoed(log types.Log) (*DelayedVetoableVetoed, error) { - event := new(DelayedVetoableVetoed) - if err := _DelayedVetoable.contract.UnpackLog(event, "Vetoed", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/op-e2e/bindings/soulgastoken.go b/op-e2e/bindings/soulgastoken.go new file mode 100644 index 0000000000000..4930a2bba88c0 --- /dev/null +++ b/op-e2e/bindings/soulgastoken.go @@ -0,0 +1,1795 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bindings + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// SoulGasTokenMetaData contains all meta data concerning the SoulGasToken contract. +var SoulGasTokenMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_isBackedByNative\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"addBurners\",\"inputs\":[{\"name\":\"_burners\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"addMinters\",\"inputs\":[{\"name\":\"_minters\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"allowSgtValue\",\"inputs\":[{\"name\":\"_contracts\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"allowance\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"approve\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batchBurnFrom\",\"inputs\":[{\"name\":\"_accounts\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"_values\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"batchDepositFor\",\"inputs\":[{\"name\":\"_accounts\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"_values\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"batchDepositForAll\",\"inputs\":[{\"name\":\"_accounts\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"_value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"batchMint\",\"inputs\":[{\"name\":\"_accounts\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"_values\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"batchWithdrawFrom\",\"inputs\":[{\"name\":\"_accounts\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"_values\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"burnFrom\",\"inputs\":[{\"name\":\"_account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"chargeFromOrigin\",\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"amountCharged_\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"decreaseAllowance\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"subtractedValue\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"delBurners\",\"inputs\":[{\"name\":\"_burners\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"delMinters\",\"inputs\":[{\"name\":\"_minters\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deposit\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"disallowSgtValue\",\"inputs\":[{\"name\":\"_contracts\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"increaseAllowance\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"addedValue\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"_symbol\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isBackedByNative\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"name\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"symbol\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"totalSupply\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transfer\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferFrom\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"withdrawFrom\",\"inputs\":[{\"name\":\"_account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"AllowSgtValue\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"DisallowSgtValue\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", +} + +// SoulGasTokenABI is the input ABI used to generate the binding from. +// Deprecated: Use SoulGasTokenMetaData.ABI instead. +var SoulGasTokenABI = SoulGasTokenMetaData.ABI + +// SoulGasToken is an auto generated Go binding around an Ethereum contract. +type SoulGasToken struct { + SoulGasTokenCaller // Read-only binding to the contract + SoulGasTokenTransactor // Write-only binding to the contract + SoulGasTokenFilterer // Log filterer for contract events +} + +// SoulGasTokenCaller is an auto generated read-only Go binding around an Ethereum contract. +type SoulGasTokenCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SoulGasTokenTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SoulGasTokenTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SoulGasTokenFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SoulGasTokenFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SoulGasTokenSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SoulGasTokenSession struct { + Contract *SoulGasToken // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SoulGasTokenCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SoulGasTokenCallerSession struct { + Contract *SoulGasTokenCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SoulGasTokenTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SoulGasTokenTransactorSession struct { + Contract *SoulGasTokenTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SoulGasTokenRaw is an auto generated low-level Go binding around an Ethereum contract. +type SoulGasTokenRaw struct { + Contract *SoulGasToken // Generic contract binding to access the raw methods on +} + +// SoulGasTokenCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SoulGasTokenCallerRaw struct { + Contract *SoulGasTokenCaller // Generic read-only contract binding to access the raw methods on +} + +// SoulGasTokenTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SoulGasTokenTransactorRaw struct { + Contract *SoulGasTokenTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSoulGasToken creates a new instance of SoulGasToken, bound to a specific deployed contract. +func NewSoulGasToken(address common.Address, backend bind.ContractBackend) (*SoulGasToken, error) { + contract, err := bindSoulGasToken(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SoulGasToken{SoulGasTokenCaller: SoulGasTokenCaller{contract: contract}, SoulGasTokenTransactor: SoulGasTokenTransactor{contract: contract}, SoulGasTokenFilterer: SoulGasTokenFilterer{contract: contract}}, nil +} + +// NewSoulGasTokenCaller creates a new read-only instance of SoulGasToken, bound to a specific deployed contract. +func NewSoulGasTokenCaller(address common.Address, caller bind.ContractCaller) (*SoulGasTokenCaller, error) { + contract, err := bindSoulGasToken(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SoulGasTokenCaller{contract: contract}, nil +} + +// NewSoulGasTokenTransactor creates a new write-only instance of SoulGasToken, bound to a specific deployed contract. +func NewSoulGasTokenTransactor(address common.Address, transactor bind.ContractTransactor) (*SoulGasTokenTransactor, error) { + contract, err := bindSoulGasToken(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SoulGasTokenTransactor{contract: contract}, nil +} + +// NewSoulGasTokenFilterer creates a new log filterer instance of SoulGasToken, bound to a specific deployed contract. +func NewSoulGasTokenFilterer(address common.Address, filterer bind.ContractFilterer) (*SoulGasTokenFilterer, error) { + contract, err := bindSoulGasToken(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SoulGasTokenFilterer{contract: contract}, nil +} + +// bindSoulGasToken binds a generic wrapper to an already deployed contract. +func bindSoulGasToken(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := SoulGasTokenMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SoulGasToken *SoulGasTokenRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SoulGasToken.Contract.SoulGasTokenCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SoulGasToken *SoulGasTokenRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SoulGasToken.Contract.SoulGasTokenTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SoulGasToken *SoulGasTokenRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SoulGasToken.Contract.SoulGasTokenTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SoulGasToken *SoulGasTokenCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SoulGasToken.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SoulGasToken *SoulGasTokenTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SoulGasToken.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SoulGasToken *SoulGasTokenTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SoulGasToken.Contract.contract.Transact(opts, method, params...) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_SoulGasToken *SoulGasTokenCaller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "allowance", owner, spender) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_SoulGasToken *SoulGasTokenSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _SoulGasToken.Contract.Allowance(&_SoulGasToken.CallOpts, owner, spender) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_SoulGasToken *SoulGasTokenCallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _SoulGasToken.Contract.Allowance(&_SoulGasToken.CallOpts, owner, spender) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_SoulGasToken *SoulGasTokenCaller) BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "balanceOf", account) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_SoulGasToken *SoulGasTokenSession) BalanceOf(account common.Address) (*big.Int, error) { + return _SoulGasToken.Contract.BalanceOf(&_SoulGasToken.CallOpts, account) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_SoulGasToken *SoulGasTokenCallerSession) BalanceOf(account common.Address) (*big.Int, error) { + return _SoulGasToken.Contract.BalanceOf(&_SoulGasToken.CallOpts, account) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_SoulGasToken *SoulGasTokenCaller) Decimals(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_SoulGasToken *SoulGasTokenSession) Decimals() (uint8, error) { + return _SoulGasToken.Contract.Decimals(&_SoulGasToken.CallOpts) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_SoulGasToken *SoulGasTokenCallerSession) Decimals() (uint8, error) { + return _SoulGasToken.Contract.Decimals(&_SoulGasToken.CallOpts) +} + +// IsBackedByNative is a free data retrieval call binding the contract method 0xbbd10120. +// +// Solidity: function isBackedByNative() view returns(bool) +func (_SoulGasToken *SoulGasTokenCaller) IsBackedByNative(opts *bind.CallOpts) (bool, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "isBackedByNative") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsBackedByNative is a free data retrieval call binding the contract method 0xbbd10120. +// +// Solidity: function isBackedByNative() view returns(bool) +func (_SoulGasToken *SoulGasTokenSession) IsBackedByNative() (bool, error) { + return _SoulGasToken.Contract.IsBackedByNative(&_SoulGasToken.CallOpts) +} + +// IsBackedByNative is a free data retrieval call binding the contract method 0xbbd10120. +// +// Solidity: function isBackedByNative() view returns(bool) +func (_SoulGasToken *SoulGasTokenCallerSession) IsBackedByNative() (bool, error) { + return _SoulGasToken.Contract.IsBackedByNative(&_SoulGasToken.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_SoulGasToken *SoulGasTokenCaller) Name(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "name") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_SoulGasToken *SoulGasTokenSession) Name() (string, error) { + return _SoulGasToken.Contract.Name(&_SoulGasToken.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_SoulGasToken *SoulGasTokenCallerSession) Name() (string, error) { + return _SoulGasToken.Contract.Name(&_SoulGasToken.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_SoulGasToken *SoulGasTokenCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_SoulGasToken *SoulGasTokenSession) Owner() (common.Address, error) { + return _SoulGasToken.Contract.Owner(&_SoulGasToken.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_SoulGasToken *SoulGasTokenCallerSession) Owner() (common.Address, error) { + return _SoulGasToken.Contract.Owner(&_SoulGasToken.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_SoulGasToken *SoulGasTokenCaller) Symbol(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "symbol") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_SoulGasToken *SoulGasTokenSession) Symbol() (string, error) { + return _SoulGasToken.Contract.Symbol(&_SoulGasToken.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_SoulGasToken *SoulGasTokenCallerSession) Symbol() (string, error) { + return _SoulGasToken.Contract.Symbol(&_SoulGasToken.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_SoulGasToken *SoulGasTokenCaller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SoulGasToken.contract.Call(opts, &out, "totalSupply") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_SoulGasToken *SoulGasTokenSession) TotalSupply() (*big.Int, error) { + return _SoulGasToken.Contract.TotalSupply(&_SoulGasToken.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_SoulGasToken *SoulGasTokenCallerSession) TotalSupply() (*big.Int, error) { + return _SoulGasToken.Contract.TotalSupply(&_SoulGasToken.CallOpts) +} + +// AddBurners is a paid mutator transaction binding the contract method 0x3ab84dd9. +// +// Solidity: function addBurners(address[] _burners) returns() +func (_SoulGasToken *SoulGasTokenTransactor) AddBurners(opts *bind.TransactOpts, _burners []common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "addBurners", _burners) +} + +// AddBurners is a paid mutator transaction binding the contract method 0x3ab84dd9. +// +// Solidity: function addBurners(address[] _burners) returns() +func (_SoulGasToken *SoulGasTokenSession) AddBurners(_burners []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.AddBurners(&_SoulGasToken.TransactOpts, _burners) +} + +// AddBurners is a paid mutator transaction binding the contract method 0x3ab84dd9. +// +// Solidity: function addBurners(address[] _burners) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) AddBurners(_burners []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.AddBurners(&_SoulGasToken.TransactOpts, _burners) +} + +// AddMinters is a paid mutator transaction binding the contract method 0x71e2a657. +// +// Solidity: function addMinters(address[] _minters) returns() +func (_SoulGasToken *SoulGasTokenTransactor) AddMinters(opts *bind.TransactOpts, _minters []common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "addMinters", _minters) +} + +// AddMinters is a paid mutator transaction binding the contract method 0x71e2a657. +// +// Solidity: function addMinters(address[] _minters) returns() +func (_SoulGasToken *SoulGasTokenSession) AddMinters(_minters []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.AddMinters(&_SoulGasToken.TransactOpts, _minters) +} + +// AddMinters is a paid mutator transaction binding the contract method 0x71e2a657. +// +// Solidity: function addMinters(address[] _minters) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) AddMinters(_minters []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.AddMinters(&_SoulGasToken.TransactOpts, _minters) +} + +// AllowSgtValue is a paid mutator transaction binding the contract method 0x674e29ea. +// +// Solidity: function allowSgtValue(address[] _contracts) returns() +func (_SoulGasToken *SoulGasTokenTransactor) AllowSgtValue(opts *bind.TransactOpts, _contracts []common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "allowSgtValue", _contracts) +} + +// AllowSgtValue is a paid mutator transaction binding the contract method 0x674e29ea. +// +// Solidity: function allowSgtValue(address[] _contracts) returns() +func (_SoulGasToken *SoulGasTokenSession) AllowSgtValue(_contracts []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.AllowSgtValue(&_SoulGasToken.TransactOpts, _contracts) +} + +// AllowSgtValue is a paid mutator transaction binding the contract method 0x674e29ea. +// +// Solidity: function allowSgtValue(address[] _contracts) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) AllowSgtValue(_contracts []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.AllowSgtValue(&_SoulGasToken.TransactOpts, _contracts) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactor) Approve(opts *bind.TransactOpts, arg0 common.Address, arg1 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "approve", arg0, arg1) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenSession) Approve(arg0 common.Address, arg1 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.Approve(&_SoulGasToken.TransactOpts, arg0, arg1) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactorSession) Approve(arg0 common.Address, arg1 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.Approve(&_SoulGasToken.TransactOpts, arg0, arg1) +} + +// BatchBurnFrom is a paid mutator transaction binding the contract method 0x1b9a7529. +// +// Solidity: function batchBurnFrom(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenTransactor) BatchBurnFrom(opts *bind.TransactOpts, _accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "batchBurnFrom", _accounts, _values) +} + +// BatchBurnFrom is a paid mutator transaction binding the contract method 0x1b9a7529. +// +// Solidity: function batchBurnFrom(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenSession) BatchBurnFrom(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchBurnFrom(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BatchBurnFrom is a paid mutator transaction binding the contract method 0x1b9a7529. +// +// Solidity: function batchBurnFrom(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) BatchBurnFrom(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchBurnFrom(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BatchDepositFor is a paid mutator transaction binding the contract method 0x299f8170. +// +// Solidity: function batchDepositFor(address[] _accounts, uint256[] _values) payable returns() +func (_SoulGasToken *SoulGasTokenTransactor) BatchDepositFor(opts *bind.TransactOpts, _accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "batchDepositFor", _accounts, _values) +} + +// BatchDepositFor is a paid mutator transaction binding the contract method 0x299f8170. +// +// Solidity: function batchDepositFor(address[] _accounts, uint256[] _values) payable returns() +func (_SoulGasToken *SoulGasTokenSession) BatchDepositFor(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchDepositFor(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BatchDepositFor is a paid mutator transaction binding the contract method 0x299f8170. +// +// Solidity: function batchDepositFor(address[] _accounts, uint256[] _values) payable returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) BatchDepositFor(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchDepositFor(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BatchDepositForAll is a paid mutator transaction binding the contract method 0x84e08810. +// +// Solidity: function batchDepositForAll(address[] _accounts, uint256 _value) payable returns() +func (_SoulGasToken *SoulGasTokenTransactor) BatchDepositForAll(opts *bind.TransactOpts, _accounts []common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "batchDepositForAll", _accounts, _value) +} + +// BatchDepositForAll is a paid mutator transaction binding the contract method 0x84e08810. +// +// Solidity: function batchDepositForAll(address[] _accounts, uint256 _value) payable returns() +func (_SoulGasToken *SoulGasTokenSession) BatchDepositForAll(_accounts []common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchDepositForAll(&_SoulGasToken.TransactOpts, _accounts, _value) +} + +// BatchDepositForAll is a paid mutator transaction binding the contract method 0x84e08810. +// +// Solidity: function batchDepositForAll(address[] _accounts, uint256 _value) payable returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) BatchDepositForAll(_accounts []common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchDepositForAll(&_SoulGasToken.TransactOpts, _accounts, _value) +} + +// BatchMint is a paid mutator transaction binding the contract method 0x68573107. +// +// Solidity: function batchMint(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenTransactor) BatchMint(opts *bind.TransactOpts, _accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "batchMint", _accounts, _values) +} + +// BatchMint is a paid mutator transaction binding the contract method 0x68573107. +// +// Solidity: function batchMint(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenSession) BatchMint(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchMint(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BatchMint is a paid mutator transaction binding the contract method 0x68573107. +// +// Solidity: function batchMint(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) BatchMint(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchMint(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BatchWithdrawFrom is a paid mutator transaction binding the contract method 0xb3e2a832. +// +// Solidity: function batchWithdrawFrom(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenTransactor) BatchWithdrawFrom(opts *bind.TransactOpts, _accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "batchWithdrawFrom", _accounts, _values) +} + +// BatchWithdrawFrom is a paid mutator transaction binding the contract method 0xb3e2a832. +// +// Solidity: function batchWithdrawFrom(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenSession) BatchWithdrawFrom(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchWithdrawFrom(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BatchWithdrawFrom is a paid mutator transaction binding the contract method 0xb3e2a832. +// +// Solidity: function batchWithdrawFrom(address[] _accounts, uint256[] _values) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) BatchWithdrawFrom(_accounts []common.Address, _values []*big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BatchWithdrawFrom(&_SoulGasToken.TransactOpts, _accounts, _values) +} + +// BurnFrom is a paid mutator transaction binding the contract method 0x79cc6790. +// +// Solidity: function burnFrom(address _account, uint256 _value) returns() +func (_SoulGasToken *SoulGasTokenTransactor) BurnFrom(opts *bind.TransactOpts, _account common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "burnFrom", _account, _value) +} + +// BurnFrom is a paid mutator transaction binding the contract method 0x79cc6790. +// +// Solidity: function burnFrom(address _account, uint256 _value) returns() +func (_SoulGasToken *SoulGasTokenSession) BurnFrom(_account common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BurnFrom(&_SoulGasToken.TransactOpts, _account, _value) +} + +// BurnFrom is a paid mutator transaction binding the contract method 0x79cc6790. +// +// Solidity: function burnFrom(address _account, uint256 _value) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) BurnFrom(_account common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.BurnFrom(&_SoulGasToken.TransactOpts, _account, _value) +} + +// ChargeFromOrigin is a paid mutator transaction binding the contract method 0xce25c861. +// +// Solidity: function chargeFromOrigin(uint256 _amount) returns(uint256 amountCharged_) +func (_SoulGasToken *SoulGasTokenTransactor) ChargeFromOrigin(opts *bind.TransactOpts, _amount *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "chargeFromOrigin", _amount) +} + +// ChargeFromOrigin is a paid mutator transaction binding the contract method 0xce25c861. +// +// Solidity: function chargeFromOrigin(uint256 _amount) returns(uint256 amountCharged_) +func (_SoulGasToken *SoulGasTokenSession) ChargeFromOrigin(_amount *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.ChargeFromOrigin(&_SoulGasToken.TransactOpts, _amount) +} + +// ChargeFromOrigin is a paid mutator transaction binding the contract method 0xce25c861. +// +// Solidity: function chargeFromOrigin(uint256 _amount) returns(uint256 amountCharged_) +func (_SoulGasToken *SoulGasTokenTransactorSession) ChargeFromOrigin(_amount *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.ChargeFromOrigin(&_SoulGasToken.TransactOpts, _amount) +} + +// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7. +// +// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactor) DecreaseAllowance(opts *bind.TransactOpts, spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "decreaseAllowance", spender, subtractedValue) +} + +// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7. +// +// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool) +func (_SoulGasToken *SoulGasTokenSession) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.DecreaseAllowance(&_SoulGasToken.TransactOpts, spender, subtractedValue) +} + +// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7. +// +// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactorSession) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.DecreaseAllowance(&_SoulGasToken.TransactOpts, spender, subtractedValue) +} + +// DelBurners is a paid mutator transaction binding the contract method 0xb8de86b4. +// +// Solidity: function delBurners(address[] _burners) returns() +func (_SoulGasToken *SoulGasTokenTransactor) DelBurners(opts *bind.TransactOpts, _burners []common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "delBurners", _burners) +} + +// DelBurners is a paid mutator transaction binding the contract method 0xb8de86b4. +// +// Solidity: function delBurners(address[] _burners) returns() +func (_SoulGasToken *SoulGasTokenSession) DelBurners(_burners []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.DelBurners(&_SoulGasToken.TransactOpts, _burners) +} + +// DelBurners is a paid mutator transaction binding the contract method 0xb8de86b4. +// +// Solidity: function delBurners(address[] _burners) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) DelBurners(_burners []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.DelBurners(&_SoulGasToken.TransactOpts, _burners) +} + +// DelMinters is a paid mutator transaction binding the contract method 0xe04b8180. +// +// Solidity: function delMinters(address[] _minters) returns() +func (_SoulGasToken *SoulGasTokenTransactor) DelMinters(opts *bind.TransactOpts, _minters []common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "delMinters", _minters) +} + +// DelMinters is a paid mutator transaction binding the contract method 0xe04b8180. +// +// Solidity: function delMinters(address[] _minters) returns() +func (_SoulGasToken *SoulGasTokenSession) DelMinters(_minters []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.DelMinters(&_SoulGasToken.TransactOpts, _minters) +} + +// DelMinters is a paid mutator transaction binding the contract method 0xe04b8180. +// +// Solidity: function delMinters(address[] _minters) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) DelMinters(_minters []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.DelMinters(&_SoulGasToken.TransactOpts, _minters) +} + +// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. +// +// Solidity: function deposit() payable returns() +func (_SoulGasToken *SoulGasTokenTransactor) Deposit(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "deposit") +} + +// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. +// +// Solidity: function deposit() payable returns() +func (_SoulGasToken *SoulGasTokenSession) Deposit() (*types.Transaction, error) { + return _SoulGasToken.Contract.Deposit(&_SoulGasToken.TransactOpts) +} + +// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. +// +// Solidity: function deposit() payable returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) Deposit() (*types.Transaction, error) { + return _SoulGasToken.Contract.Deposit(&_SoulGasToken.TransactOpts) +} + +// DisallowSgtValue is a paid mutator transaction binding the contract method 0xdc270eb8. +// +// Solidity: function disallowSgtValue(address[] _contracts) returns() +func (_SoulGasToken *SoulGasTokenTransactor) DisallowSgtValue(opts *bind.TransactOpts, _contracts []common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "disallowSgtValue", _contracts) +} + +// DisallowSgtValue is a paid mutator transaction binding the contract method 0xdc270eb8. +// +// Solidity: function disallowSgtValue(address[] _contracts) returns() +func (_SoulGasToken *SoulGasTokenSession) DisallowSgtValue(_contracts []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.DisallowSgtValue(&_SoulGasToken.TransactOpts, _contracts) +} + +// DisallowSgtValue is a paid mutator transaction binding the contract method 0xdc270eb8. +// +// Solidity: function disallowSgtValue(address[] _contracts) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) DisallowSgtValue(_contracts []common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.DisallowSgtValue(&_SoulGasToken.TransactOpts, _contracts) +} + +// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351. +// +// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactor) IncreaseAllowance(opts *bind.TransactOpts, spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "increaseAllowance", spender, addedValue) +} + +// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351. +// +// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool) +func (_SoulGasToken *SoulGasTokenSession) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.IncreaseAllowance(&_SoulGasToken.TransactOpts, spender, addedValue) +} + +// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351. +// +// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactorSession) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.IncreaseAllowance(&_SoulGasToken.TransactOpts, spender, addedValue) +} + +// Initialize is a paid mutator transaction binding the contract method 0x077f224a. +// +// Solidity: function initialize(string _name, string _symbol, address _owner) returns() +func (_SoulGasToken *SoulGasTokenTransactor) Initialize(opts *bind.TransactOpts, _name string, _symbol string, _owner common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "initialize", _name, _symbol, _owner) +} + +// Initialize is a paid mutator transaction binding the contract method 0x077f224a. +// +// Solidity: function initialize(string _name, string _symbol, address _owner) returns() +func (_SoulGasToken *SoulGasTokenSession) Initialize(_name string, _symbol string, _owner common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.Initialize(&_SoulGasToken.TransactOpts, _name, _symbol, _owner) +} + +// Initialize is a paid mutator transaction binding the contract method 0x077f224a. +// +// Solidity: function initialize(string _name, string _symbol, address _owner) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) Initialize(_name string, _symbol string, _owner common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.Initialize(&_SoulGasToken.TransactOpts, _name, _symbol, _owner) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_SoulGasToken *SoulGasTokenTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_SoulGasToken *SoulGasTokenSession) RenounceOwnership() (*types.Transaction, error) { + return _SoulGasToken.Contract.RenounceOwnership(&_SoulGasToken.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _SoulGasToken.Contract.RenounceOwnership(&_SoulGasToken.TransactOpts) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactor) Transfer(opts *bind.TransactOpts, arg0 common.Address, arg1 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "transfer", arg0, arg1) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenSession) Transfer(arg0 common.Address, arg1 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.Transfer(&_SoulGasToken.TransactOpts, arg0, arg1) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactorSession) Transfer(arg0 common.Address, arg1 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.Transfer(&_SoulGasToken.TransactOpts, arg0, arg1) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address , address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactor) TransferFrom(opts *bind.TransactOpts, arg0 common.Address, arg1 common.Address, arg2 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "transferFrom", arg0, arg1, arg2) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address , address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenSession) TransferFrom(arg0 common.Address, arg1 common.Address, arg2 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.TransferFrom(&_SoulGasToken.TransactOpts, arg0, arg1, arg2) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address , address , uint256 ) returns(bool) +func (_SoulGasToken *SoulGasTokenTransactorSession) TransferFrom(arg0 common.Address, arg1 common.Address, arg2 *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.TransferFrom(&_SoulGasToken.TransactOpts, arg0, arg1, arg2) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_SoulGasToken *SoulGasTokenTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_SoulGasToken *SoulGasTokenSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.TransferOwnership(&_SoulGasToken.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _SoulGasToken.Contract.TransferOwnership(&_SoulGasToken.TransactOpts, newOwner) +} + +// WithdrawFrom is a paid mutator transaction binding the contract method 0x9470b0bd. +// +// Solidity: function withdrawFrom(address _account, uint256 _value) returns() +func (_SoulGasToken *SoulGasTokenTransactor) WithdrawFrom(opts *bind.TransactOpts, _account common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.contract.Transact(opts, "withdrawFrom", _account, _value) +} + +// WithdrawFrom is a paid mutator transaction binding the contract method 0x9470b0bd. +// +// Solidity: function withdrawFrom(address _account, uint256 _value) returns() +func (_SoulGasToken *SoulGasTokenSession) WithdrawFrom(_account common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.WithdrawFrom(&_SoulGasToken.TransactOpts, _account, _value) +} + +// WithdrawFrom is a paid mutator transaction binding the contract method 0x9470b0bd. +// +// Solidity: function withdrawFrom(address _account, uint256 _value) returns() +func (_SoulGasToken *SoulGasTokenTransactorSession) WithdrawFrom(_account common.Address, _value *big.Int) (*types.Transaction, error) { + return _SoulGasToken.Contract.WithdrawFrom(&_SoulGasToken.TransactOpts, _account, _value) +} + +// SoulGasTokenAllowSgtValueIterator is returned from FilterAllowSgtValue and is used to iterate over the raw logs and unpacked data for AllowSgtValue events raised by the SoulGasToken contract. +type SoulGasTokenAllowSgtValueIterator struct { + Event *SoulGasTokenAllowSgtValue // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SoulGasTokenAllowSgtValueIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenAllowSgtValue) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenAllowSgtValue) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SoulGasTokenAllowSgtValueIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SoulGasTokenAllowSgtValueIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SoulGasTokenAllowSgtValue represents a AllowSgtValue event raised by the SoulGasToken contract. +type SoulGasTokenAllowSgtValue struct { + From common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAllowSgtValue is a free log retrieval operation binding the contract event 0xf135aca2ee4483470b8f44f38ab676fc36fc67437777f3c520e5fbeb3706009f. +// +// Solidity: event AllowSgtValue(address indexed from) +func (_SoulGasToken *SoulGasTokenFilterer) FilterAllowSgtValue(opts *bind.FilterOpts, from []common.Address) (*SoulGasTokenAllowSgtValueIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _SoulGasToken.contract.FilterLogs(opts, "AllowSgtValue", fromRule) + if err != nil { + return nil, err + } + return &SoulGasTokenAllowSgtValueIterator{contract: _SoulGasToken.contract, event: "AllowSgtValue", logs: logs, sub: sub}, nil +} + +// WatchAllowSgtValue is a free log subscription operation binding the contract event 0xf135aca2ee4483470b8f44f38ab676fc36fc67437777f3c520e5fbeb3706009f. +// +// Solidity: event AllowSgtValue(address indexed from) +func (_SoulGasToken *SoulGasTokenFilterer) WatchAllowSgtValue(opts *bind.WatchOpts, sink chan<- *SoulGasTokenAllowSgtValue, from []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _SoulGasToken.contract.WatchLogs(opts, "AllowSgtValue", fromRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SoulGasTokenAllowSgtValue) + if err := _SoulGasToken.contract.UnpackLog(event, "AllowSgtValue", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAllowSgtValue is a log parse operation binding the contract event 0xf135aca2ee4483470b8f44f38ab676fc36fc67437777f3c520e5fbeb3706009f. +// +// Solidity: event AllowSgtValue(address indexed from) +func (_SoulGasToken *SoulGasTokenFilterer) ParseAllowSgtValue(log types.Log) (*SoulGasTokenAllowSgtValue, error) { + event := new(SoulGasTokenAllowSgtValue) + if err := _SoulGasToken.contract.UnpackLog(event, "AllowSgtValue", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SoulGasTokenApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the SoulGasToken contract. +type SoulGasTokenApprovalIterator struct { + Event *SoulGasTokenApproval // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SoulGasTokenApprovalIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenApproval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SoulGasTokenApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SoulGasTokenApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SoulGasTokenApproval represents a Approval event raised by the SoulGasToken contract. +type SoulGasTokenApproval struct { + Owner common.Address + Spender common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_SoulGasToken *SoulGasTokenFilterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*SoulGasTokenApprovalIterator, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _SoulGasToken.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return &SoulGasTokenApprovalIterator{contract: _SoulGasToken.contract, event: "Approval", logs: logs, sub: sub}, nil +} + +// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_SoulGasToken *SoulGasTokenFilterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *SoulGasTokenApproval, owner []common.Address, spender []common.Address) (event.Subscription, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _SoulGasToken.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SoulGasTokenApproval) + if err := _SoulGasToken.contract.UnpackLog(event, "Approval", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_SoulGasToken *SoulGasTokenFilterer) ParseApproval(log types.Log) (*SoulGasTokenApproval, error) { + event := new(SoulGasTokenApproval) + if err := _SoulGasToken.contract.UnpackLog(event, "Approval", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SoulGasTokenDisallowSgtValueIterator is returned from FilterDisallowSgtValue and is used to iterate over the raw logs and unpacked data for DisallowSgtValue events raised by the SoulGasToken contract. +type SoulGasTokenDisallowSgtValueIterator struct { + Event *SoulGasTokenDisallowSgtValue // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SoulGasTokenDisallowSgtValueIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenDisallowSgtValue) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenDisallowSgtValue) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SoulGasTokenDisallowSgtValueIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SoulGasTokenDisallowSgtValueIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SoulGasTokenDisallowSgtValue represents a DisallowSgtValue event raised by the SoulGasToken contract. +type SoulGasTokenDisallowSgtValue struct { + From common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDisallowSgtValue is a free log retrieval operation binding the contract event 0x42d3350598a4a2ec6e60463e0bffa1aab494a9e8d4484b017270dde628b4edb1. +// +// Solidity: event DisallowSgtValue(address indexed from) +func (_SoulGasToken *SoulGasTokenFilterer) FilterDisallowSgtValue(opts *bind.FilterOpts, from []common.Address) (*SoulGasTokenDisallowSgtValueIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _SoulGasToken.contract.FilterLogs(opts, "DisallowSgtValue", fromRule) + if err != nil { + return nil, err + } + return &SoulGasTokenDisallowSgtValueIterator{contract: _SoulGasToken.contract, event: "DisallowSgtValue", logs: logs, sub: sub}, nil +} + +// WatchDisallowSgtValue is a free log subscription operation binding the contract event 0x42d3350598a4a2ec6e60463e0bffa1aab494a9e8d4484b017270dde628b4edb1. +// +// Solidity: event DisallowSgtValue(address indexed from) +func (_SoulGasToken *SoulGasTokenFilterer) WatchDisallowSgtValue(opts *bind.WatchOpts, sink chan<- *SoulGasTokenDisallowSgtValue, from []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + + logs, sub, err := _SoulGasToken.contract.WatchLogs(opts, "DisallowSgtValue", fromRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SoulGasTokenDisallowSgtValue) + if err := _SoulGasToken.contract.UnpackLog(event, "DisallowSgtValue", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseDisallowSgtValue is a log parse operation binding the contract event 0x42d3350598a4a2ec6e60463e0bffa1aab494a9e8d4484b017270dde628b4edb1. +// +// Solidity: event DisallowSgtValue(address indexed from) +func (_SoulGasToken *SoulGasTokenFilterer) ParseDisallowSgtValue(log types.Log) (*SoulGasTokenDisallowSgtValue, error) { + event := new(SoulGasTokenDisallowSgtValue) + if err := _SoulGasToken.contract.UnpackLog(event, "DisallowSgtValue", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SoulGasTokenInitializedIterator is returned from FilterInitialized and is used to iterate over the raw logs and unpacked data for Initialized events raised by the SoulGasToken contract. +type SoulGasTokenInitializedIterator struct { + Event *SoulGasTokenInitialized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SoulGasTokenInitializedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SoulGasTokenInitializedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SoulGasTokenInitializedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SoulGasTokenInitialized represents a Initialized event raised by the SoulGasToken contract. +type SoulGasTokenInitialized struct { + Version uint8 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterInitialized is a free log retrieval operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_SoulGasToken *SoulGasTokenFilterer) FilterInitialized(opts *bind.FilterOpts) (*SoulGasTokenInitializedIterator, error) { + + logs, sub, err := _SoulGasToken.contract.FilterLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return &SoulGasTokenInitializedIterator{contract: _SoulGasToken.contract, event: "Initialized", logs: logs, sub: sub}, nil +} + +// WatchInitialized is a free log subscription operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_SoulGasToken *SoulGasTokenFilterer) WatchInitialized(opts *bind.WatchOpts, sink chan<- *SoulGasTokenInitialized) (event.Subscription, error) { + + logs, sub, err := _SoulGasToken.contract.WatchLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SoulGasTokenInitialized) + if err := _SoulGasToken.contract.UnpackLog(event, "Initialized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseInitialized is a log parse operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_SoulGasToken *SoulGasTokenFilterer) ParseInitialized(log types.Log) (*SoulGasTokenInitialized, error) { + event := new(SoulGasTokenInitialized) + if err := _SoulGasToken.contract.UnpackLog(event, "Initialized", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SoulGasTokenOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the SoulGasToken contract. +type SoulGasTokenOwnershipTransferredIterator struct { + Event *SoulGasTokenOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SoulGasTokenOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SoulGasTokenOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SoulGasTokenOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SoulGasTokenOwnershipTransferred represents a OwnershipTransferred event raised by the SoulGasToken contract. +type SoulGasTokenOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_SoulGasToken *SoulGasTokenFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*SoulGasTokenOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _SoulGasToken.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &SoulGasTokenOwnershipTransferredIterator{contract: _SoulGasToken.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_SoulGasToken *SoulGasTokenFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *SoulGasTokenOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _SoulGasToken.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SoulGasTokenOwnershipTransferred) + if err := _SoulGasToken.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_SoulGasToken *SoulGasTokenFilterer) ParseOwnershipTransferred(log types.Log) (*SoulGasTokenOwnershipTransferred, error) { + event := new(SoulGasTokenOwnershipTransferred) + if err := _SoulGasToken.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SoulGasTokenTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the SoulGasToken contract. +type SoulGasTokenTransferIterator struct { + Event *SoulGasTokenTransfer // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SoulGasTokenTransferIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SoulGasTokenTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SoulGasTokenTransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SoulGasTokenTransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SoulGasTokenTransfer represents a Transfer event raised by the SoulGasToken contract. +type SoulGasTokenTransfer struct { + From common.Address + To common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_SoulGasToken *SoulGasTokenFilterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*SoulGasTokenTransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _SoulGasToken.contract.FilterLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return &SoulGasTokenTransferIterator{contract: _SoulGasToken.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_SoulGasToken *SoulGasTokenFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *SoulGasTokenTransfer, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _SoulGasToken.contract.WatchLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SoulGasTokenTransfer) + if err := _SoulGasToken.contract.UnpackLog(event, "Transfer", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_SoulGasToken *SoulGasTokenFilterer) ParseTransfer(log types.Log) (*SoulGasTokenTransfer, error) { + event := new(SoulGasTokenTransfer) + if err := _SoulGasToken.contract.UnpackLog(event, "Transfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/op-e2e/bindings/systemconfig.go b/op-e2e/bindings/systemconfig.go index 28b7ff6beace9..a6ff5ce1480e6 100644 --- a/op-e2e/bindings/systemconfig.go +++ b/op-e2e/bindings/systemconfig.go @@ -28,8 +28,8 @@ var ( _ = event.NewSubscription ) -// ResourceMeteringResourceConfig is an auto generated low-level Go binding around an user-defined struct. -type ResourceMeteringResourceConfig struct { +// IResourceMeteringResourceConfig is an auto generated low-level Go binding around an user-defined struct. +type IResourceMeteringResourceConfig struct { MaxResourceLimit uint32 ElasticityMultiplier uint8 BaseFeeMaxChangeDenominator uint8 @@ -51,7 +51,7 @@ type SystemConfigAddresses struct { // SystemConfigMetaData contains all meta data concerning the SystemConfig contract. var SystemConfigMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"BATCH_INBOX_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DISPUTE_GAME_FACTORY_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_CROSS_DOMAIN_MESSENGER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_ERC_721_BRIDGE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_STANDARD_BRIDGE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPTIMISM_PORTAL_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"START_BLOCK_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"UNSAFE_BLOCK_SIGNER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"VERSION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"basefeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batchInbox\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batcherHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blobbasefeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disputeGameFactory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasPayingToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"},{\"internalType\":\"uint8\",\"name\":\"decimals_\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasPayingTokenName\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"name_\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasPayingTokenSymbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"symbol_\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_basefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structResourceMetering.ResourceConfig\",\"name\":\"_config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_batchInbox\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"l1CrossDomainMessenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1ERC721Bridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1StandardBridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"disputeGameFactory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismPortal\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismMintableERC20Factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"gasPayingToken\",\"type\":\"address\"}],\"internalType\":\"structSystemConfig.Addresses\",\"name\":\"_addresses\",\"type\":\"tuple\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isCustomGasToken\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1CrossDomainMessenger\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1ERC721Bridge\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1StandardBridge\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maximumGasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minimumGasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismMintableERC20Factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismPortal\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"overhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resourceConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structResourceMetering.ResourceConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scalar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"}],\"name\":\"setBatcherHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"}],\"name\":\"setGasConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_basefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\"}],\"name\":\"setGasConfigEcotone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"}],\"name\":\"setGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"}],\"name\":\"setUnsafeBlockSigner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"startBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"startBlock_\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unsafeBlockSigner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"enumSystemConfig.UpdateType\",\"name\":\"updateType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"ConfigUpdate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"}]", + ABI: "[{\"type\":\"constructor\",\"inputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"BATCH_INBOX_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"DISPUTE_GAME_FACTORY_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"L1_CROSS_DOMAIN_MESSENGER_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"L1_ERC_721_BRIDGE_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"L1_STANDARD_BRIDGE_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"OPTIMISM_PORTAL_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"START_BLOCK_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"UNSAFE_BLOCK_SIGNER_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"VERSION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"basefeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batchInbox\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batcherHash\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobbasefeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"disputeGameFactory\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip1559Denominator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip1559Elasticity\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasLimit\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasPayingToken\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals_\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasPayingTokenName\",\"inputs\":[],\"outputs\":[{\"name\":\"name_\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasPayingTokenSymbol\",\"inputs\":[],\"outputs\":[{\"name\":\"symbol_\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_basefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_batcherHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_gasLimit\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_unsafeBlockSigner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_config\",\"type\":\"tuple\",\"internalType\":\"structIResourceMetering.ResourceConfig\",\"components\":[{\"name\":\"maxResourceLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"elasticityMultiplier\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"minimumBaseFee\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"systemTxMaxGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maximumBaseFee\",\"type\":\"uint128\",\"internalType\":\"uint128\"}]},{\"name\":\"_batchInbox\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_addresses\",\"type\":\"tuple\",\"internalType\":\"structSystemConfig.Addresses\",\"components\":[{\"name\":\"l1CrossDomainMessenger\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"l1ERC721Bridge\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"l1StandardBridge\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"disputeGameFactory\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"optimismPortal\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"optimismMintableERC20Factory\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"gasPayingToken\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isCustomGasToken\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1CrossDomainMessenger\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1ERC721Bridge\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1StandardBridge\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"maximumGasLimit\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"minimumGasLimit\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"optimismMintableERC20Factory\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"optimismPortal\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"overhead\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"resourceConfig\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIResourceMetering.ResourceConfig\",\"components\":[{\"name\":\"maxResourceLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"elasticityMultiplier\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"minimumBaseFee\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"systemTxMaxGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maximumBaseFee\",\"type\":\"uint128\",\"internalType\":\"uint128\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"scalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setBatcherHash\",\"inputs\":[{\"name\":\"_batcherHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setEIP1559Params\",\"inputs\":[{\"name\":\"_denominator\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_elasticity\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setGasConfig\",\"inputs\":[{\"name\":\"_overhead\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_scalar\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setGasConfigEcotone\",\"inputs\":[{\"name\":\"_basefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setGasLimit\",\"inputs\":[{\"name\":\"_gasLimit\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnsafeBlockSigner\",\"inputs\":[{\"name\":\"_unsafeBlockSigner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"startBlock\",\"inputs\":[],\"outputs\":[{\"name\":\"startBlock_\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unsafeBlockSigner\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"pure\"},{\"type\":\"event\",\"name\":\"ConfigUpdate\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"updateType\",\"type\":\"uint8\",\"indexed\":true,\"internalType\":\"enumSystemConfig.UpdateType\"},{\"name\":\"data\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false}]", } // SystemConfigABI is the input ABI used to generate the binding from. @@ -665,6 +665,68 @@ func (_SystemConfig *SystemConfigCallerSession) DisputeGameFactory() (common.Add return _SystemConfig.Contract.DisputeGameFactory(&_SystemConfig.CallOpts) } +// Eip1559Denominator is a free data retrieval call binding the contract method 0xd220a9e0. +// +// Solidity: function eip1559Denominator() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) Eip1559Denominator(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "eip1559Denominator") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// Eip1559Denominator is a free data retrieval call binding the contract method 0xd220a9e0. +// +// Solidity: function eip1559Denominator() view returns(uint32) +func (_SystemConfig *SystemConfigSession) Eip1559Denominator() (uint32, error) { + return _SystemConfig.Contract.Eip1559Denominator(&_SystemConfig.CallOpts) +} + +// Eip1559Denominator is a free data retrieval call binding the contract method 0xd220a9e0. +// +// Solidity: function eip1559Denominator() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) Eip1559Denominator() (uint32, error) { + return _SystemConfig.Contract.Eip1559Denominator(&_SystemConfig.CallOpts) +} + +// Eip1559Elasticity is a free data retrieval call binding the contract method 0xc9ff2d16. +// +// Solidity: function eip1559Elasticity() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) Eip1559Elasticity(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "eip1559Elasticity") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// Eip1559Elasticity is a free data retrieval call binding the contract method 0xc9ff2d16. +// +// Solidity: function eip1559Elasticity() view returns(uint32) +func (_SystemConfig *SystemConfigSession) Eip1559Elasticity() (uint32, error) { + return _SystemConfig.Contract.Eip1559Elasticity(&_SystemConfig.CallOpts) +} + +// Eip1559Elasticity is a free data retrieval call binding the contract method 0xc9ff2d16. +// +// Solidity: function eip1559Elasticity() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) Eip1559Elasticity() (uint32, error) { + return _SystemConfig.Contract.Eip1559Elasticity(&_SystemConfig.CallOpts) +} + // GasLimit is a free data retrieval call binding the contract method 0xf68016b7. // // Solidity: function gasLimit() view returns(uint64) @@ -1116,15 +1178,15 @@ func (_SystemConfig *SystemConfigCallerSession) Owner() (common.Address, error) // ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02. // // Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128)) -func (_SystemConfig *SystemConfigCaller) ResourceConfig(opts *bind.CallOpts) (ResourceMeteringResourceConfig, error) { +func (_SystemConfig *SystemConfigCaller) ResourceConfig(opts *bind.CallOpts) (IResourceMeteringResourceConfig, error) { var out []interface{} err := _SystemConfig.contract.Call(opts, &out, "resourceConfig") if err != nil { - return *new(ResourceMeteringResourceConfig), err + return *new(IResourceMeteringResourceConfig), err } - out0 := *abi.ConvertType(out[0], new(ResourceMeteringResourceConfig)).(*ResourceMeteringResourceConfig) + out0 := *abi.ConvertType(out[0], new(IResourceMeteringResourceConfig)).(*IResourceMeteringResourceConfig) return out0, err @@ -1133,14 +1195,14 @@ func (_SystemConfig *SystemConfigCaller) ResourceConfig(opts *bind.CallOpts) (Re // ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02. // // Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128)) -func (_SystemConfig *SystemConfigSession) ResourceConfig() (ResourceMeteringResourceConfig, error) { +func (_SystemConfig *SystemConfigSession) ResourceConfig() (IResourceMeteringResourceConfig, error) { return _SystemConfig.Contract.ResourceConfig(&_SystemConfig.CallOpts) } // ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02. // // Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128)) -func (_SystemConfig *SystemConfigCallerSession) ResourceConfig() (ResourceMeteringResourceConfig, error) { +func (_SystemConfig *SystemConfigCallerSession) ResourceConfig() (IResourceMeteringResourceConfig, error) { return _SystemConfig.Contract.ResourceConfig(&_SystemConfig.CallOpts) } @@ -1239,7 +1301,7 @@ func (_SystemConfig *SystemConfigCallerSession) UnsafeBlockSigner() (common.Addr // Version is a free data retrieval call binding the contract method 0x54fd4d50. // -// Solidity: function version() view returns(string) +// Solidity: function version() pure returns(string) func (_SystemConfig *SystemConfigCaller) Version(opts *bind.CallOpts) (string, error) { var out []interface{} err := _SystemConfig.contract.Call(opts, &out, "version") @@ -1256,14 +1318,14 @@ func (_SystemConfig *SystemConfigCaller) Version(opts *bind.CallOpts) (string, e // Version is a free data retrieval call binding the contract method 0x54fd4d50. // -// Solidity: function version() view returns(string) +// Solidity: function version() pure returns(string) func (_SystemConfig *SystemConfigSession) Version() (string, error) { return _SystemConfig.Contract.Version(&_SystemConfig.CallOpts) } // Version is a free data retrieval call binding the contract method 0x54fd4d50. // -// Solidity: function version() view returns(string) +// Solidity: function version() pure returns(string) func (_SystemConfig *SystemConfigCallerSession) Version() (string, error) { return _SystemConfig.Contract.Version(&_SystemConfig.CallOpts) } @@ -1271,21 +1333,21 @@ func (_SystemConfig *SystemConfigCallerSession) Version() (string, error) { // Initialize is a paid mutator transaction binding the contract method 0xdb9040fa. // // Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses) returns() -func (_SystemConfig *SystemConfigTransactor) Initialize(opts *bind.TransactOpts, _owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config ResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { +func (_SystemConfig *SystemConfigTransactor) Initialize(opts *bind.TransactOpts, _owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { return _SystemConfig.contract.Transact(opts, "initialize", _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses) } // Initialize is a paid mutator transaction binding the contract method 0xdb9040fa. // // Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses) returns() -func (_SystemConfig *SystemConfigSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config ResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { +func (_SystemConfig *SystemConfigSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses) } // Initialize is a paid mutator transaction binding the contract method 0xdb9040fa. // // Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses) returns() -func (_SystemConfig *SystemConfigTransactorSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config ResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { +func (_SystemConfig *SystemConfigTransactorSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses) } @@ -1331,6 +1393,27 @@ func (_SystemConfig *SystemConfigTransactorSession) SetBatcherHash(_batcherHash return _SystemConfig.Contract.SetBatcherHash(&_SystemConfig.TransactOpts, _batcherHash) } +// SetEIP1559Params is a paid mutator transaction binding the contract method 0xc0fd4b41. +// +// Solidity: function setEIP1559Params(uint32 _denominator, uint32 _elasticity) returns() +func (_SystemConfig *SystemConfigTransactor) SetEIP1559Params(opts *bind.TransactOpts, _denominator uint32, _elasticity uint32) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setEIP1559Params", _denominator, _elasticity) +} + +// SetEIP1559Params is a paid mutator transaction binding the contract method 0xc0fd4b41. +// +// Solidity: function setEIP1559Params(uint32 _denominator, uint32 _elasticity) returns() +func (_SystemConfig *SystemConfigSession) SetEIP1559Params(_denominator uint32, _elasticity uint32) (*types.Transaction, error) { + return _SystemConfig.Contract.SetEIP1559Params(&_SystemConfig.TransactOpts, _denominator, _elasticity) +} + +// SetEIP1559Params is a paid mutator transaction binding the contract method 0xc0fd4b41. +// +// Solidity: function setEIP1559Params(uint32 _denominator, uint32 _elasticity) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetEIP1559Params(_denominator uint32, _elasticity uint32) (*types.Transaction, error) { + return _SystemConfig.Contract.SetEIP1559Params(&_SystemConfig.TransactOpts, _denominator, _elasticity) +} + // SetGasConfig is a paid mutator transaction binding the contract method 0x935f029e. // // Solidity: function setGasConfig(uint256 _overhead, uint256 _scalar) returns() diff --git a/op-e2e/config/init.go b/op-e2e/config/init.go index a635c9efd7e55..032f70bf2a883 100644 --- a/op-e2e/config/init.go +++ b/op-e2e/config/init.go @@ -185,6 +185,7 @@ func initAllocType(root string, allocType AllocType) { } l2Alloc[mode] = allocs } + mustL2Allocs(genesis.L2AllocsHolocene) mustL2Allocs(genesis.L2AllocsGranite) mustL2Allocs(genesis.L2AllocsFjord) mustL2Allocs(genesis.L2AllocsEcotone) @@ -200,9 +201,6 @@ func initAllocType(root string, allocType AllocType) { panic(err) } - // Do not use clique in the in memory tests. Otherwise block building - // would be much more complex. - dc.L1UseClique = false // Set the L1 genesis block timestamp to now dc.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix()) dc.FundDevAccounts = true diff --git a/op-e2e/e2e.go b/op-e2e/e2e.go index 54533cf4fef9f..f603b97f6fbda 100644 --- a/op-e2e/e2e.go +++ b/op-e2e/e2e.go @@ -52,6 +52,13 @@ func UsesCannon(t e2eutils.TestingBase) { } } +// IsSlow indicates that the test is too expensive to run on the main CI workflow +func IsSlow(t e2eutils.TestingBase) { + if os.Getenv("OP_E2E_SKIP_SLOW_TEST") == "true" { + t.Skip("Skipping slow test") + } +} + type executorInfo struct { total uint64 idx uint64 diff --git a/op-e2e/e2eutils/challenger/helper.go b/op-e2e/e2eutils/challenger/helper.go index 8e31f1311e870..925e38856ed99 100644 --- a/op-e2e/e2eutils/challenger/helper.go +++ b/op-e2e/e2eutils/challenger/helper.go @@ -190,6 +190,7 @@ func NewChallengerConfig(t *testing.T, sys EndpointProvider, l2NodeName string, l1Endpoint := sys.NodeEndpoint("l1").RPC() l1Beacon := sys.L1BeaconEndpoint().RestHTTP() cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, sys.RollupEndpoint(l2NodeName).RPC(), sys.NodeEndpoint(l2NodeName).RPC(), t.TempDir()) + cfg.Cannon.L2Custom = true // The devnet can't set the absolute prestate output root because the contracts are deployed in L1 genesis // before the L2 genesis is known. cfg.AllowInvalidPrestate = true diff --git a/op-e2e/e2eutils/disputegame/helper.go b/op-e2e/e2eutils/disputegame/helper.go index 07ee4c659ff90..658eedffc5145 100644 --- a/op-e2e/e2eutils/disputegame/helper.go +++ b/op-e2e/e2eutils/disputegame/helper.go @@ -291,7 +291,7 @@ func (h *FactoryHelper) WaitForBlock(l2Node string, l2BlockNumber uint64, cfg *G l2Client := h.System.NodeClient(l2Node) if cfg.allowUnsafe { - _, err := geth.WaitForBlock(new(big.Int).SetUint64(l2BlockNumber), l2Client, 1*time.Minute) + _, err := geth.WaitForBlock(new(big.Int).SetUint64(l2BlockNumber), l2Client, geth.WithAbsoluteTimeout(time.Minute)) h.Require.NoErrorf(err, "Block number %v did not become unsafe", l2BlockNumber) } else { _, err := geth.WaitForBlockToBeSafe(new(big.Int).SetUint64(l2BlockNumber), l2Client, 1*time.Minute) diff --git a/op-e2e/e2eutils/disputegame/output_cannon_helper.go b/op-e2e/e2eutils/disputegame/output_cannon_helper.go index 264742be194be..43316a185fd3d 100644 --- a/op-e2e/e2eutils/disputegame/output_cannon_helper.go +++ b/op-e2e/e2eutils/disputegame/output_cannon_helper.go @@ -89,7 +89,7 @@ func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node s prestateProvider := outputs.NewPrestateProvider(rollupClient, actorCfg.PrestateBlock) l1Head := g.GetL1Head(ctx) accessor, err := outputs.NewOutputCannonTraceAccessor( - logger, metrics.NoopMetrics, cfg.Cannon, vm.NewOpProgramServerExecutor(), l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, actorCfg.PrestateBlock, actorCfg.PoststateBlock) + logger, metrics.NoopMetrics, cfg.Cannon, vm.NewOpProgramServerExecutor(logger), l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, actorCfg.PrestateBlock, actorCfg.PoststateBlock) g.Require.NoError(err, "Failed to create output cannon trace accessor") return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, accessor) } diff --git a/op-e2e/e2eutils/fakebeacon/blobs.go b/op-e2e/e2eutils/fakebeacon/blobs.go index a96042bc7add4..4ec5169267425 100644 --- a/op-e2e/e2eutils/fakebeacon/blobs.go +++ b/op-e2e/e2eutils/fakebeacon/blobs.go @@ -127,6 +127,7 @@ func (f *FakeBeacon) Start(addr string) error { Slot: eth.Uint64String(slot), }, }, + InclusionProof: make([]eth.Bytes32, 0), } copy(sidecars[i].Blob[:], bundle.Blobs[ix]) } diff --git a/op-e2e/e2eutils/geth/geth.go b/op-e2e/e2eutils/geth/geth.go index de5b12b77d3c7..b210949803bb6 100644 --- a/op-e2e/e2eutils/geth/geth.go +++ b/op-e2e/e2eutils/geth/geth.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/txpool/blobpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" @@ -49,8 +50,8 @@ func InitL1(blockTime uint64, finalizedDistance uint64, genesis *core.Genesis, c HTTPPort: 0, WSHost: "127.0.0.1", WSPort: 0, - WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, - HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, + WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine", "miner"}, + HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine", "miner"}, } gethInstance, err := createGethNode(false, nodeConfig, ethConfig, opts...) @@ -82,8 +83,8 @@ func defaultNodeConfig(name string, jwtPath string) *node.Config { AuthPort: 0, HTTPHost: "127.0.0.1", HTTPPort: 0, - WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, - HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, + WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine", "miner"}, + HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine", "miner"}, JWTSecret: jwtPath, } } @@ -104,6 +105,9 @@ func InitL2(name string, genesis *core.Genesis, jwtPath string, opts ...GethOpti // enough to build blocks within 1 second, but high enough to avoid unnecessary test CPU cycles. Recommit: time.Millisecond * 400, }, + TxPool: legacypool.Config{ + NoLocals: true, + }, } nodeConfig := defaultNodeConfig(fmt.Sprintf("l2-geth-%v", name), jwtPath) return createGethNode(true, nodeConfig, ethConfig, opts...) diff --git a/op-e2e/e2eutils/geth/wait.go b/op-e2e/e2eutils/geth/wait.go index 267e589f021c1..8356058afda75 100644 --- a/op-e2e/e2eutils/geth/wait.go +++ b/op-e2e/e2eutils/geth/wait.go @@ -17,7 +17,10 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -const errStrTxIdxingInProgress = "transaction indexing is in progress" +const ( + errStrTxIdxingInProgress = "transaction indexing is in progress" + waitForBlockMaxRetries = 3 +) // errTimeout represents a timeout var errTimeout = errors.New("timeout") @@ -83,25 +86,80 @@ func WaitForTransaction(hash common.Hash, client *ethclient.Client, timeout time } } -func WaitForBlock(number *big.Int, client *ethclient.Client, timeout time.Duration) (*types.Block, error) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() +type waitForBlockOptions struct { + noChangeTimeout time.Duration + absoluteTimeout time.Duration +} - headChan := make(chan *types.Header, 100) - headSub, err := client.SubscribeNewHead(ctx, headChan) - if err != nil { - return nil, err +func WithNoChangeTimeout(timeout time.Duration) WaitForBlockOption { + return func(o *waitForBlockOptions) { + o.noChangeTimeout = timeout } - defer headSub.Unsubscribe() +} + +func WithAbsoluteTimeout(timeout time.Duration) WaitForBlockOption { + return func(o *waitForBlockOptions) { + o.absoluteTimeout = timeout + } +} + +type WaitForBlockOption func(*waitForBlockOptions) + +// WaitForBlock waits for the chain to advance to the provided block number. It can be configured with +// two different timeout: an absolute timeout, and a no change timeout. The absolute timeout caps +// the maximum amount of time this method will run. The no change timeout will return an error if the +// block number does not change within that time window. This is useful to bail out early in the event +// of a stuck chain, but allow things to continue if the chain is still advancing. +// +// This function will also retry fetch errors up to three times before returning an error in order to +// protect against transient network problems. This function uses polling rather than websockets. +func WaitForBlock(number *big.Int, client *ethclient.Client, opts ...WaitForBlockOption) (*types.Block, error) { + defaultOpts := &waitForBlockOptions{ + noChangeTimeout: 30 * time.Second, + absoluteTimeout: 3 * time.Minute, + } + for _, opt := range opts { + opt(defaultOpts) + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultOpts.absoluteTimeout) + defer cancel() + + lastAdvancement := time.Now() + lastBlockNumber := big.NewInt(0) + + pollTicker := time.NewTicker(500 * time.Millisecond) + defer pollTicker.Stop() + var errCount int for { - select { - case head := <-headChan: - if head.Number.Cmp(number) >= 0 { - return client.BlockByNumber(ctx, number) + head, err := client.BlockByNumber(ctx, nil) + if err != nil { + errCount++ + if errCount >= waitForBlockMaxRetries { + return nil, fmt.Errorf("head fetching exceeded max retries. last error: %w", err) } - case err := <-headSub.Err(): - return nil, fmt.Errorf("error in head subscription: %w", err) + continue + } + + errCount = 0 + + if head.Number().Cmp(number) >= 0 { + return client.BlockByNumber(ctx, number) + } + + if head.Number().Cmp(lastBlockNumber) != 0 { + lastBlockNumber = head.Number() + lastAdvancement = time.Now() + } + + if time.Since(lastAdvancement) > defaultOpts.noChangeTimeout { + return nil, fmt.Errorf("block number %d has not changed in %s", lastBlockNumber, defaultOpts.noChangeTimeout) + } + + select { + case <-pollTicker.C: + continue case <-ctx.Done(): return nil, ctx.Err() } diff --git a/op-e2e/interop/contracts/emit.go b/op-e2e/e2eutils/interop/contracts/bindings/emit/emit.go similarity index 99% rename from op-e2e/interop/contracts/emit.go rename to op-e2e/e2eutils/interop/contracts/bindings/emit/emit.go index ceae2412659bc..7755ef5fd18fd 100644 --- a/op-e2e/interop/contracts/emit.go +++ b/op-e2e/e2eutils/interop/contracts/bindings/emit/emit.go @@ -26,7 +26,6 @@ var ( _ = common.Big1 _ = types.BloomLookup _ = event.NewSubscription - _ = abi.ConvertType ) // EmitMetaData contains all meta data concerning the Emit contract. @@ -157,11 +156,11 @@ func NewEmitFilterer(address common.Address, filterer bind.ContractFilterer) (*E // bindEmit binds a generic wrapper to an already deployed contract. func bindEmit(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := EmitMetaData.GetAbi() + parsed, err := abi.JSON(strings.NewReader(EmitABI)) if err != nil { return nil, err } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil } // Call invokes the (constant) contract method with params as input values and diff --git a/op-e2e/e2eutils/interop/contracts/bindings/inbox/inbox.go b/op-e2e/e2eutils/interop/contracts/bindings/inbox/inbox.go new file mode 100644 index 0000000000000..f73fd56355e4e --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/bindings/inbox/inbox.go @@ -0,0 +1,231 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package inbox + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// Identifier is an auto generated low-level Go binding around an user-defined struct. +type Identifier struct { + Origin common.Address + BlockNumber *big.Int + LogIndex *big.Int + Timestamp *big.Int + ChainId *big.Int +} + +// InboxMetaData contains all meta data concerning the Inbox contract. +var InboxMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"executeMessage\",\"inputs\":[{\"name\":\"_id\",\"type\":\"tuple\",\"internalType\":\"structIdentifier\",\"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\":\"_target\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_message\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"validateMessage\",\"inputs\":[{\"name\":\"_id\",\"type\":\"tuple\",\"internalType\":\"structIdentifier\",\"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\"}]", +} + +// InboxABI is the input ABI used to generate the binding from. +// Deprecated: Use InboxMetaData.ABI instead. +var InboxABI = InboxMetaData.ABI + +// Inbox is an auto generated Go binding around an Ethereum contract. +type Inbox struct { + InboxCaller // Read-only binding to the contract + InboxTransactor // Write-only binding to the contract + InboxFilterer // Log filterer for contract events +} + +// InboxCaller is an auto generated read-only Go binding around an Ethereum contract. +type InboxCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// InboxTransactor is an auto generated write-only Go binding around an Ethereum contract. +type InboxTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// InboxFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type InboxFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// InboxSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type InboxSession struct { + Contract *Inbox // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// InboxCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type InboxCallerSession struct { + Contract *InboxCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// InboxTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type InboxTransactorSession struct { + Contract *InboxTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// InboxRaw is an auto generated low-level Go binding around an Ethereum contract. +type InboxRaw struct { + Contract *Inbox // Generic contract binding to access the raw methods on +} + +// InboxCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type InboxCallerRaw struct { + Contract *InboxCaller // Generic read-only contract binding to access the raw methods on +} + +// InboxTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type InboxTransactorRaw struct { + Contract *InboxTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewInbox creates a new instance of Inbox, bound to a specific deployed contract. +func NewInbox(address common.Address, backend bind.ContractBackend) (*Inbox, error) { + contract, err := bindInbox(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Inbox{InboxCaller: InboxCaller{contract: contract}, InboxTransactor: InboxTransactor{contract: contract}, InboxFilterer: InboxFilterer{contract: contract}}, nil +} + +// NewInboxCaller creates a new read-only instance of Inbox, bound to a specific deployed contract. +func NewInboxCaller(address common.Address, caller bind.ContractCaller) (*InboxCaller, error) { + contract, err := bindInbox(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &InboxCaller{contract: contract}, nil +} + +// NewInboxTransactor creates a new write-only instance of Inbox, bound to a specific deployed contract. +func NewInboxTransactor(address common.Address, transactor bind.ContractTransactor) (*InboxTransactor, error) { + contract, err := bindInbox(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &InboxTransactor{contract: contract}, nil +} + +// NewInboxFilterer creates a new log filterer instance of Inbox, bound to a specific deployed contract. +func NewInboxFilterer(address common.Address, filterer bind.ContractFilterer) (*InboxFilterer, error) { + contract, err := bindInbox(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &InboxFilterer{contract: contract}, nil +} + +// bindInbox binds a generic wrapper to an already deployed contract. +func bindInbox(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(InboxABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Inbox *InboxRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Inbox.Contract.InboxCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Inbox *InboxRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Inbox.Contract.InboxTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Inbox *InboxRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Inbox.Contract.InboxTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Inbox *InboxCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Inbox.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Inbox *InboxTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Inbox.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Inbox *InboxTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Inbox.Contract.contract.Transact(opts, method, params...) +} + +// ExecuteMessage is a paid mutator transaction binding the contract method 0x5984c53e. +// +// Solidity: function executeMessage((address,uint256,uint256,uint256,uint256) _id, address _target, bytes _message) payable returns() +func (_Inbox *InboxTransactor) ExecuteMessage(opts *bind.TransactOpts, _id Identifier, _target common.Address, _message []byte) (*types.Transaction, error) { + return _Inbox.contract.Transact(opts, "executeMessage", _id, _target, _message) +} + +// ExecuteMessage is a paid mutator transaction binding the contract method 0x5984c53e. +// +// Solidity: function executeMessage((address,uint256,uint256,uint256,uint256) _id, address _target, bytes _message) payable returns() +func (_Inbox *InboxSession) ExecuteMessage(_id Identifier, _target common.Address, _message []byte) (*types.Transaction, error) { + return _Inbox.Contract.ExecuteMessage(&_Inbox.TransactOpts, _id, _target, _message) +} + +// ExecuteMessage is a paid mutator transaction binding the contract method 0x5984c53e. +// +// Solidity: function executeMessage((address,uint256,uint256,uint256,uint256) _id, address _target, bytes _message) payable returns() +func (_Inbox *InboxTransactorSession) ExecuteMessage(_id Identifier, _target common.Address, _message []byte) (*types.Transaction, error) { + return _Inbox.Contract.ExecuteMessage(&_Inbox.TransactOpts, _id, _target, _message) +} + +// ValidateMessage is a paid mutator transaction binding the contract method 0xab4d6f75. +// +// Solidity: function validateMessage((address,uint256,uint256,uint256,uint256) _id, bytes32 _msgHash) returns() +func (_Inbox *InboxTransactor) ValidateMessage(opts *bind.TransactOpts, _id Identifier, _msgHash [32]byte) (*types.Transaction, error) { + return _Inbox.contract.Transact(opts, "validateMessage", _id, _msgHash) +} + +// ValidateMessage is a paid mutator transaction binding the contract method 0xab4d6f75. +// +// Solidity: function validateMessage((address,uint256,uint256,uint256,uint256) _id, bytes32 _msgHash) returns() +func (_Inbox *InboxSession) ValidateMessage(_id Identifier, _msgHash [32]byte) (*types.Transaction, error) { + return _Inbox.Contract.ValidateMessage(&_Inbox.TransactOpts, _id, _msgHash) +} + +// ValidateMessage is a paid mutator transaction binding the contract method 0xab4d6f75. +// +// Solidity: function validateMessage((address,uint256,uint256,uint256,uint256) _id, bytes32 _msgHash) returns() +func (_Inbox *InboxTransactorSession) ValidateMessage(_id Identifier, _msgHash [32]byte) (*types.Transaction, error) { + return _Inbox.Contract.ValidateMessage(&_Inbox.TransactOpts, _id, _msgHash) +} diff --git a/op-e2e/e2eutils/interop/contracts/bindings/systemconfig/systemconfig.go b/op-e2e/e2eutils/interop/contracts/bindings/systemconfig/systemconfig.go new file mode 100644 index 0000000000000..9e29cc4700e59 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/bindings/systemconfig/systemconfig.go @@ -0,0 +1,201 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package systemconfig + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// SystemconfigMetaData contains all meta data concerning the Systemconfig contract. +var SystemconfigMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"addDependency\",\"inputs\":[{\"name\":\"_chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"}]", +} + +// SystemconfigABI is the input ABI used to generate the binding from. +// Deprecated: Use SystemconfigMetaData.ABI instead. +var SystemconfigABI = SystemconfigMetaData.ABI + +// Systemconfig is an auto generated Go binding around an Ethereum contract. +type Systemconfig struct { + SystemconfigCaller // Read-only binding to the contract + SystemconfigTransactor // Write-only binding to the contract + SystemconfigFilterer // Log filterer for contract events +} + +// SystemconfigCaller is an auto generated read-only Go binding around an Ethereum contract. +type SystemconfigCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SystemconfigTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SystemconfigTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SystemconfigFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SystemconfigFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SystemconfigSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SystemconfigSession struct { + Contract *Systemconfig // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SystemconfigCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SystemconfigCallerSession struct { + Contract *SystemconfigCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SystemconfigTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SystemconfigTransactorSession struct { + Contract *SystemconfigTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SystemconfigRaw is an auto generated low-level Go binding around an Ethereum contract. +type SystemconfigRaw struct { + Contract *Systemconfig // Generic contract binding to access the raw methods on +} + +// SystemconfigCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SystemconfigCallerRaw struct { + Contract *SystemconfigCaller // Generic read-only contract binding to access the raw methods on +} + +// SystemconfigTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SystemconfigTransactorRaw struct { + Contract *SystemconfigTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSystemconfig creates a new instance of Systemconfig, bound to a specific deployed contract. +func NewSystemconfig(address common.Address, backend bind.ContractBackend) (*Systemconfig, error) { + contract, err := bindSystemconfig(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Systemconfig{SystemconfigCaller: SystemconfigCaller{contract: contract}, SystemconfigTransactor: SystemconfigTransactor{contract: contract}, SystemconfigFilterer: SystemconfigFilterer{contract: contract}}, nil +} + +// NewSystemconfigCaller creates a new read-only instance of Systemconfig, bound to a specific deployed contract. +func NewSystemconfigCaller(address common.Address, caller bind.ContractCaller) (*SystemconfigCaller, error) { + contract, err := bindSystemconfig(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SystemconfigCaller{contract: contract}, nil +} + +// NewSystemconfigTransactor creates a new write-only instance of Systemconfig, bound to a specific deployed contract. +func NewSystemconfigTransactor(address common.Address, transactor bind.ContractTransactor) (*SystemconfigTransactor, error) { + contract, err := bindSystemconfig(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SystemconfigTransactor{contract: contract}, nil +} + +// NewSystemconfigFilterer creates a new log filterer instance of Systemconfig, bound to a specific deployed contract. +func NewSystemconfigFilterer(address common.Address, filterer bind.ContractFilterer) (*SystemconfigFilterer, error) { + contract, err := bindSystemconfig(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SystemconfigFilterer{contract: contract}, nil +} + +// bindSystemconfig binds a generic wrapper to an already deployed contract. +func bindSystemconfig(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(SystemconfigABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Systemconfig *SystemconfigRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Systemconfig.Contract.SystemconfigCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Systemconfig *SystemconfigRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Systemconfig.Contract.SystemconfigTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Systemconfig *SystemconfigRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Systemconfig.Contract.SystemconfigTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Systemconfig *SystemconfigCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Systemconfig.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Systemconfig *SystemconfigTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Systemconfig.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Systemconfig *SystemconfigTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Systemconfig.Contract.contract.Transact(opts, method, params...) +} + +// AddDependency is a paid mutator transaction binding the contract method 0xa89c793c. +// +// Solidity: function addDependency(uint256 _chainId) returns() +func (_Systemconfig *SystemconfigTransactor) AddDependency(opts *bind.TransactOpts, _chainId *big.Int) (*types.Transaction, error) { + return _Systemconfig.contract.Transact(opts, "addDependency", _chainId) +} + +// AddDependency is a paid mutator transaction binding the contract method 0xa89c793c. +// +// Solidity: function addDependency(uint256 _chainId) returns() +func (_Systemconfig *SystemconfigSession) AddDependency(_chainId *big.Int) (*types.Transaction, error) { + return _Systemconfig.Contract.AddDependency(&_Systemconfig.TransactOpts, _chainId) +} + +// AddDependency is a paid mutator transaction binding the contract method 0xa89c793c. +// +// Solidity: function addDependency(uint256 _chainId) returns() +func (_Systemconfig *SystemconfigTransactorSession) AddDependency(_chainId *big.Int) (*types.Transaction, error) { + return _Systemconfig.Contract.AddDependency(&_Systemconfig.TransactOpts, _chainId) +} diff --git a/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.abi b/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.abi new file mode 100644 index 0000000000000..b724169ec6fa4 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.abi @@ -0,0 +1,97 @@ +[ + { + "type": "function", + "name": "executeMessage", + "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": "_target", + "type": "address", + "internalType": "address" + }, + { + "name": "_message", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "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" + } +] diff --git a/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.bin b/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.bin new file mode 100644 index 0000000000000..ec687260b8517 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.bin @@ -0,0 +1 @@ +0x diff --git a/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.json b/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.json new file mode 100644 index 0000000000000..96c8d6ddb036c --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/ICrossL2Inbox.sol/ICrossL2Inbox.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"executeMessage","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":"_target","type":"address","internalType":"address"},{"name":"_message","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"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"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"executeMessage((address,uint256,uint256,uint256,uint256),address,bytes)":"5984c53e","validateMessage((address,uint256,uint256,uint256,uint256),bytes32)":"ab4d6f75"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"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\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"}],\"name\":\"executeMessage\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"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\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"executeMessage((address,uint256,uint256,uint256,uint256),address,bytes)\":{\"params\":{\"_id\":\"An Identifier pointing to the initiating message.\",\"_message\":\"The message payload, matching the initiating message.\",\"_target\":\"Account that is called with _msg.\"}},\"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)\":{\"params\":{\"_id\":\"Identifier of the message.\",\"_msgHash\":\"Hash of the message payload to call target with.\"}}},\"title\":\"ICrossL2Inbox\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"executeMessage((address,uint256,uint256,uint256,uint256),address,bytes)\":{\"notice\":\"Executes a cross chain message on the destination chain.\"},\"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)\":{\"notice\":\"Validates a cross chain message on the destination chain and emits an ExecutingMessage event. This function is useful for applications that understand the schema of the _message payload and want to process it in a custom way.\"}},\"notice\":\"Interface for the CrossL2Inbox contract.\",\"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\":\"0x97daf76e4a10b96d8062e71df352cbfa7577593fa96676fd43b40f4a0aca9b7f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://74518c974318070da4e118cd28aa86d7f0702ed8cccc39d60906b93197687248\",\"dweb:/ipfs/QmWimBXYsDWDtNgzhjaLUx4xEiTMvbx5635E2TfNBKoV1E\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.25+commit.b61c2a91"},"language":"Solidity","output":{"abi":[{"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":"address","name":"_target","type":"address"},{"internalType":"bytes","name":"_message","type":"bytes"}],"stateMutability":"payable","type":"function","name":"executeMessage"},{"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"}],"devdoc":{"kind":"dev","methods":{"executeMessage((address,uint256,uint256,uint256,uint256),address,bytes)":{"params":{"_id":"An Identifier pointing to the initiating message.","_message":"The message payload, matching the initiating message.","_target":"Account that is called with _msg."}},"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)":{"params":{"_id":"Identifier of the message.","_msgHash":"Hash of the message payload to call target with."}}},"version":1},"userdoc":{"kind":"user","methods":{"executeMessage((address,uint256,uint256,uint256,uint256),address,bytes)":{"notice":"Executes a cross chain message on the destination chain."},"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)":{"notice":"Validates a cross chain message on the destination chain and emits an ExecutingMessage event. This function is useful for applications that understand the schema of the _message payload and want to process it in a custom way."}},"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":"0x97daf76e4a10b96d8062e71df352cbfa7577593fa96676fd43b40f4a0aca9b7f","urls":["bzz-raw://74518c974318070da4e118cd28aa86d7f0702ed8cccc39d60906b93197687248","dweb:/ipfs/QmWimBXYsDWDtNgzhjaLUx4xEiTMvbx5635E2TfNBKoV1E"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user","methods":{"executeMessage((address,uint256,uint256,uint256,uint256),address,bytes)":{"notice":"Executes a cross chain message on the destination chain."},"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)":{"notice":"Validates a cross chain message on the destination chain and emits an ExecutingMessage event. This function is useful for applications that understand the schema of the _message payload and want to process it in a custom way."}},"notice":"Interface for the CrossL2Inbox contract."},"devdoc":{"version":1,"kind":"dev","methods":{"executeMessage((address,uint256,uint256,uint256,uint256),address,bytes)":{"params":{"_id":"An Identifier pointing to the initiating message.","_message":"The message payload, matching the initiating message.","_target":"Account that is called with _msg."}},"validateMessage((address,uint256,uint256,uint256,uint256),bytes32)":{"params":{"_id":"Identifier of the message.","_msgHash":"Hash of the message payload to call target with."}}},"title":"ICrossL2Inbox"},"ast":{"absolutePath":"src/ICrossL2Inbox.sol","id":35,"exportedSymbols":{"ICrossL2Inbox":[34],"Identifier":[12]},"nodeType":"SourceUnit","src":"32:1153:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","^","0.8",".0"]},{"id":12,"nodeType":"StructDefinition","src":"57:132:0","nodes":[],"canonicalName":"Identifier","members":[{"constant":false,"id":3,"mutability":"mutable","name":"origin","nameLocation":"89:6:0","nodeType":"VariableDeclaration","scope":12,"src":"81:14:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":2,"name":"address","nodeType":"ElementaryTypeName","src":"81:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":5,"mutability":"mutable","name":"blockNumber","nameLocation":"109:11:0","nodeType":"VariableDeclaration","scope":12,"src":"101:19:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":4,"name":"uint256","nodeType":"ElementaryTypeName","src":"101:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":7,"mutability":"mutable","name":"logIndex","nameLocation":"134:8:0","nodeType":"VariableDeclaration","scope":12,"src":"126:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":6,"name":"uint256","nodeType":"ElementaryTypeName","src":"126:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":9,"mutability":"mutable","name":"timestamp","nameLocation":"156:9:0","nodeType":"VariableDeclaration","scope":12,"src":"148:17:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":8,"name":"uint256","nodeType":"ElementaryTypeName","src":"148:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"},{"constant":false,"id":11,"mutability":"mutable","name":"chainId","nameLocation":"179:7:0","nodeType":"VariableDeclaration","scope":12,"src":"171:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":10,"name":"uint256","nodeType":"ElementaryTypeName","src":"171:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"name":"Identifier","nameLocation":"64:10:0","scope":35,"visibility":"public"},{"id":34,"nodeType":"ContractDefinition","src":"269:915:0","nodes":[{"id":24,"nodeType":"FunctionDefinition","src":"577:108:0","nodes":[],"documentation":{"id":14,"nodeType":"StructuredDocumentation","src":"300:272:0","text":"@notice Executes a cross chain message on the destination chain.\n @param _id An Identifier pointing to the initiating message.\n @param _target Account that is called with _msg.\n @param _message The message payload, matching the initiating message."},"functionSelector":"5984c53e","implemented":false,"kind":"function","modifiers":[],"name":"executeMessage","nameLocation":"586:14:0","parameters":{"id":22,"nodeType":"ParameterList","parameters":[{"constant":false,"id":17,"mutability":"mutable","name":"_id","nameLocation":"621:3:0","nodeType":"VariableDeclaration","scope":24,"src":"601:23:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$12_calldata_ptr","typeString":"struct Identifier"},"typeName":{"id":16,"nodeType":"UserDefinedTypeName","pathNode":{"id":15,"name":"Identifier","nameLocations":["601:10:0"],"nodeType":"IdentifierPath","referencedDeclaration":12,"src":"601:10:0"},"referencedDeclaration":12,"src":"601:10:0","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$12_storage_ptr","typeString":"struct Identifier"}},"visibility":"internal"},{"constant":false,"id":19,"mutability":"mutable","name":"_target","nameLocation":"634:7:0","nodeType":"VariableDeclaration","scope":24,"src":"626:15:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":18,"name":"address","nodeType":"ElementaryTypeName","src":"626:7:0","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":21,"mutability":"mutable","name":"_message","nameLocation":"658:8:0","nodeType":"VariableDeclaration","scope":24,"src":"643:23:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":20,"name":"bytes","nodeType":"ElementaryTypeName","src":"643:5:0","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"600:67:0"},"returnParameters":{"id":23,"nodeType":"ParameterList","parameters":[],"src":"684:0:0"},"scope":34,"stateMutability":"payable","virtual":false,"visibility":"external"},{"id":33,"nodeType":"FunctionDefinition","src":"1105:77:0","nodes":[],"documentation":{"id":25,"nodeType":"StructuredDocumentation","src":"691:409:0","text":"@notice Validates a cross chain message on the destination chain\n and emits an ExecutingMessage event. This function is useful\n for applications that understand the schema of the _message payload and want to\n process it in a custom way.\n @param _id Identifier of the message.\n @param _msgHash Hash of the message payload to call target with."},"functionSelector":"ab4d6f75","implemented":false,"kind":"function","modifiers":[],"name":"validateMessage","nameLocation":"1114:15:0","parameters":{"id":31,"nodeType":"ParameterList","parameters":[{"constant":false,"id":28,"mutability":"mutable","name":"_id","nameLocation":"1150:3:0","nodeType":"VariableDeclaration","scope":33,"src":"1130:23:0","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$12_calldata_ptr","typeString":"struct Identifier"},"typeName":{"id":27,"nodeType":"UserDefinedTypeName","pathNode":{"id":26,"name":"Identifier","nameLocations":["1130:10:0"],"nodeType":"IdentifierPath","referencedDeclaration":12,"src":"1130:10:0"},"referencedDeclaration":12,"src":"1130:10:0","typeDescriptions":{"typeIdentifier":"t_struct$_Identifier_$12_storage_ptr","typeString":"struct Identifier"}},"visibility":"internal"},{"constant":false,"id":30,"mutability":"mutable","name":"_msgHash","nameLocation":"1163:8:0","nodeType":"VariableDeclaration","scope":33,"src":"1155:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":29,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1155:7:0","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"1129:43:0"},"returnParameters":{"id":32,"nodeType":"ParameterList","parameters":[],"src":"1181:0:0"},"scope":34,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ICrossL2Inbox","contractDependencies":[],"contractKind":"interface","documentation":{"id":13,"nodeType":"StructuredDocumentation","src":"191:78:0","text":"@title ICrossL2Inbox\n @notice Interface for the CrossL2Inbox contract."},"fullyImplemented":false,"linearizedBaseContracts":[34],"name":"ICrossL2Inbox","nameLocation":"279:13:0","scope":35,"usedErrors":[],"usedEvents":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.abi b/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.abi new file mode 100644 index 0000000000000..7a7bffc2ca656 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.abi @@ -0,0 +1,15 @@ +[ + { + "type": "function", + "name": "addDependency", + "inputs": [ + { + "name": "_chainId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] diff --git a/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.bin b/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.bin new file mode 100644 index 0000000000000..ec687260b8517 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.bin @@ -0,0 +1 @@ +0x diff --git a/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.json b/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.json new file mode 100644 index 0000000000000..fef67d0a40fd0 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/ISystemConfig.sol/ISystemConfig.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"addDependency","inputs":[{"name":"_chainId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"addDependency(uint256)":"a89c793c"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_chainId\",\"type\":\"uint256\"}],\"name\":\"addDependency\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"addDependency(uint256)\":{\"params\":{\"_chainId\":\"Chain ID of chain to add.\"}}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"addDependency(uint256)\":{\"notice\":\"Adds a chain to the interop dependency set. Can only be called by the dependency manager.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/ISystemConfig.sol\":\"ISystemConfig\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[]},\"sources\":{\"src/ISystemConfig.sol\":{\"keccak256\":\"0x764ea7e528e9e5ab907515b4da22738a49a3d5d8ffb948def86d0cbb9e7765be\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9307f5fd1e826f8d059f4aa9141c360aef54612ba8562ec996e7f078ef1d1cf2\",\"dweb:/ipfs/QmbzFSWWLoQUyeuD6U7detgjPy36L3DHJp9BPpaUZqVs68\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.25+commit.b61c2a91"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"uint256","name":"_chainId","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"addDependency"}],"devdoc":{"kind":"dev","methods":{"addDependency(uint256)":{"params":{"_chainId":"Chain ID of chain to add."}}},"version":1},"userdoc":{"kind":"user","methods":{"addDependency(uint256)":{"notice":"Adds a chain to the interop dependency set. Can only be called by the dependency manager."}},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/ISystemConfig.sol":"ISystemConfig"},"evmVersion":"cancun","libraries":{}},"sources":{"src/ISystemConfig.sol":{"keccak256":"0x764ea7e528e9e5ab907515b4da22738a49a3d5d8ffb948def86d0cbb9e7765be","urls":["bzz-raw://9307f5fd1e826f8d059f4aa9141c360aef54612ba8562ec996e7f078ef1d1cf2","dweb:/ipfs/QmbzFSWWLoQUyeuD6U7detgjPy36L3DHJp9BPpaUZqVs68"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"userdoc":{"version":1,"kind":"user","methods":{"addDependency(uint256)":{"notice":"Adds a chain to the interop dependency set. Can only be called by the dependency manager."}}},"devdoc":{"version":1,"kind":"dev","methods":{"addDependency(uint256)":{"params":{"_chainId":"Chain ID of chain to add."}}}},"ast":{"absolutePath":"src/ISystemConfig.sol","id":9,"exportedSymbols":{"ISystemConfig":[8]},"nodeType":"SourceUnit","src":"32:264:0","nodes":[{"id":1,"nodeType":"PragmaDirective","src":"32:23:0","nodes":[],"literals":["solidity","^","0.8",".0"]},{"id":8,"nodeType":"ContractDefinition","src":"57:238:0","nodes":[{"id":7,"nodeType":"FunctionDefinition","src":"243:50:0","nodes":[],"documentation":{"id":2,"nodeType":"StructuredDocumentation","src":"87:151:0","text":"@notice Adds a chain to the interop dependency set. Can only be called by the dependency manager.\n @param _chainId Chain ID of chain to add."},"functionSelector":"a89c793c","implemented":false,"kind":"function","modifiers":[],"name":"addDependency","nameLocation":"252:13:0","parameters":{"id":5,"nodeType":"ParameterList","parameters":[{"constant":false,"id":4,"mutability":"mutable","name":"_chainId","nameLocation":"274:8:0","nodeType":"VariableDeclaration","scope":7,"src":"266:16:0","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":3,"name":"uint256","nodeType":"ElementaryTypeName","src":"266:7:0","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"265:18:0"},"returnParameters":{"id":6,"nodeType":"ParameterList","parameters":[],"src":"292:0:0"},"scope":8,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"ISystemConfig","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[8],"name":"ISystemConfig","nameLocation":"67:13:0","scope":9,"usedErrors":[],"usedEvents":[]}],"license":"MIT"},"id":0} \ No newline at end of file diff --git a/op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.abi b/op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.abi new file mode 100644 index 0000000000000..d30fa8fca93be --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.abi @@ -0,0 +1,28 @@ +[ + { + "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 + } +] diff --git a/op-e2e/interop/contracts/test-artifacts/emit.sol/EmitEvent.bin b/op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.bin similarity index 100% rename from op-e2e/interop/contracts/test-artifacts/emit.sol/EmitEvent.bin rename to op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.bin diff --git a/op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.json b/op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.json new file mode 100644 index 0000000000000..465823afa3309 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/build/emit.sol/EmitEvent.json @@ -0,0 +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":52,"exportedSymbols":{"EmitEvent":[51]},"nodeType":"SourceUnit","src":"32:305:1","nodes":[{"id":36,"nodeType":"PragmaDirective","src":"32:24:1","nodes":[],"literals":["solidity","^","0.8",".15"]},{"id":51,"nodeType":"ContractDefinition","src":"58:278:1","nodes":[{"id":40,"nodeType":"EventDefinition","src":"133:39:1","nodes":[],"anonymous":false,"eventSelector":"e00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c","name":"DataEmitted","nameLocation":"139:11:1","parameters":{"id":39,"nodeType":"ParameterList","parameters":[{"constant":false,"id":38,"indexed":true,"mutability":"mutable","name":"_data","nameLocation":"165:5:1","nodeType":"VariableDeclaration","scope":40,"src":"151:19:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":37,"name":"bytes","nodeType":"ElementaryTypeName","src":"151:5:1","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"150:21:1"}},{"id":50,"nodeType":"FunctionDefinition","src":"245:89:1","nodes":[],"body":{"id":49,"nodeType":"Block","src":"294:40:1","nodes":[],"statements":[{"eventCall":{"arguments":[{"id":46,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":42,"src":"321:5:1","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes calldata"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes calldata"}],"id":45,"name":"DataEmitted","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40,"src":"309:11:1","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_bytes_memory_ptr_$returns$__$","typeString":"function (bytes memory)"}},"id":47,"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":48,"nodeType":"EmitStatement","src":"304:23:1"}]},"functionSelector":"d836083e","implemented":true,"kind":"function","modifiers":[],"name":"emitData","nameLocation":"254:8:1","parameters":{"id":43,"nodeType":"ParameterList","parameters":[{"constant":false,"id":42,"mutability":"mutable","name":"_data","nameLocation":"278:5:1","nodeType":"VariableDeclaration","scope":50,"src":"263:20:1","stateVariable":false,"storageLocation":"calldata","typeDescriptions":{"typeIdentifier":"t_bytes_calldata_ptr","typeString":"bytes"},"typeName":{"id":41,"name":"bytes","nodeType":"ElementaryTypeName","src":"263:5:1","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"262:22:1"},"returnParameters":{"id":44,"nodeType":"ParameterList","parameters":[],"src":"294:0:1"},"scope":51,"stateMutability":"nonpayable","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"EmitEvent","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[51],"name":"EmitEvent","nameLocation":"67:9:1","scope":52,"usedErrors":[],"usedEvents":[40]}],"license":"MIT"},"id":1} \ No newline at end of file diff --git a/op-e2e/interop/contracts/foundry.toml b/op-e2e/e2eutils/interop/contracts/foundry.toml similarity index 100% rename from op-e2e/interop/contracts/foundry.toml rename to op-e2e/e2eutils/interop/contracts/foundry.toml diff --git a/op-e2e/e2eutils/interop/contracts/generate.sh b/op-e2e/e2eutils/interop/contracts/generate.sh new file mode 100755 index 0000000000000..d6107df9cc867 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/generate.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -euo + +forge build + +cd build/emit.sol +cat EmitEvent.json | jq -r '.bytecode.object' > EmitEvent.bin +cat EmitEvent.json | jq '.abi' > EmitEvent.abi +cd ../.. + +mkdir -p bindings/emit +abigen --abi ./build/emit.sol/EmitEvent.abi --bin ./build/emit.sol/EmitEvent.bin --pkg emit --out ./bindings/emit/emit.go + +cd build/ICrossL2Inbox.sol +cat ICrossL2Inbox.json | jq -r '.bytecode.object' > ICrossL2Inbox.bin +cat ICrossL2Inbox.json | jq '.abi' > ICrossL2Inbox.abi +cd ../.. + +mkdir -p bindings/inbox +abigen --abi ./build/ICrossL2Inbox.sol/ICrossL2Inbox.abi --bin ./build/ICrossL2Inbox.sol/ICrossL2Inbox.bin --pkg inbox --out ./bindings/inbox/inbox.go + +cd build/ISystemConfig.sol +cat ISystemConfig.json | jq -r '.bytecode.object' > ISystemConfig.bin +cat ISystemConfig.json | jq '.abi' > ISystemConfig.abi +cd ../.. + +mkdir -p bindings/systemconfig +abigen --abi ./build/ISystemConfig.sol/ISystemConfig.abi --bin ./build/ISystemConfig.sol/ISystemConfig.bin --pkg systemconfig --out ./bindings/systemconfig/systemconfig.go diff --git a/op-e2e/e2eutils/interop/contracts/src/ICrossL2Inbox.sol b/op-e2e/e2eutils/interop/contracts/src/ICrossL2Inbox.sol new file mode 100644 index 0000000000000..780b43d9753b0 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/src/ICrossL2Inbox.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +struct Identifier { + address origin; + uint256 blockNumber; + uint256 logIndex; + uint256 timestamp; + uint256 chainId; +} + +/// @title ICrossL2Inbox +/// @notice Interface for the CrossL2Inbox contract. +interface ICrossL2Inbox { + + /// @notice Executes a cross chain message on the destination chain. + /// @param _id An Identifier pointing to the initiating message. + /// @param _target Account that is called with _msg. + /// @param _message The message payload, matching the initiating message. + function executeMessage(Identifier calldata _id, address _target, bytes calldata _message) external payable; + + /// @notice Validates a cross chain message on the destination chain + /// and emits an ExecutingMessage event. This function is useful + /// for applications that understand the schema of the _message payload and want to + /// process it in a custom way. + /// @param _id Identifier of the message. + /// @param _msgHash Hash of the message payload to call target with. + function validateMessage(Identifier calldata _id, bytes32 _msgHash) external; +} diff --git a/op-e2e/e2eutils/interop/contracts/src/ISystemConfig.sol b/op-e2e/e2eutils/interop/contracts/src/ISystemConfig.sol new file mode 100644 index 0000000000000..fd14f919b2d0d --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/src/ISystemConfig.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ISystemConfig { + /// @notice Adds a chain to the interop dependency set. Can only be called by the dependency manager. + /// @param _chainId Chain ID of chain to add. + function addDependency(uint256 _chainId) external; +} diff --git a/op-e2e/interop/contracts/src/emit.sol b/op-e2e/e2eutils/interop/contracts/src/emit.sol similarity index 100% rename from op-e2e/interop/contracts/src/emit.sol rename to op-e2e/e2eutils/interop/contracts/src/emit.sol diff --git a/op-e2e/interop/contracts/test-artifacts/emit.sol/EmitEvent.abi b/op-e2e/e2eutils/interop/contracts/test-artifacts/emit.sol/EmitEvent.abi similarity index 100% rename from op-e2e/interop/contracts/test-artifacts/emit.sol/EmitEvent.abi rename to op-e2e/e2eutils/interop/contracts/test-artifacts/emit.sol/EmitEvent.abi diff --git a/op-e2e/e2eutils/interop/contracts/test-artifacts/emit.sol/EmitEvent.bin b/op-e2e/e2eutils/interop/contracts/test-artifacts/emit.sol/EmitEvent.bin new file mode 100644 index 0000000000000..f4c15ab05dca7 --- /dev/null +++ b/op-e2e/e2eutils/interop/contracts/test-artifacts/emit.sol/EmitEvent.bin @@ -0,0 +1 @@ +0x6080604052348015600e575f80fd5b5060ff8061001b5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063d836083e14602a575b5f80fd5b60396035366004607c565b603b565b005b8181604051604992919060e3565b604051908190038120907fe00bbfe6f6f8f1bbed2da38e3f5a139c6f9da594ab248a3cf8b44fc73627772c905f90a25050565b5f8060208385031215608c575f80fd5b823567ffffffffffffffff8082111560a2575f80fd5b818501915085601f83011260b4575f80fd5b81358181111560c1575f80fd5b86602082850101111560d1575f80fd5b60209290920196919550909350505050565b818382375f910190815291905056fea164736f6c6343000819000a diff --git a/op-e2e/interop/contracts/test-artifacts/emit.sol/EmitEvent.json b/op-e2e/e2eutils/interop/contracts/test-artifacts/emit.sol/EmitEvent.json similarity index 100% rename from op-e2e/interop/contracts/test-artifacts/emit.sol/EmitEvent.json rename to op-e2e/e2eutils/interop/contracts/test-artifacts/emit.sol/EmitEvent.json diff --git a/op-e2e/e2eutils/retryproxy/proxy.go b/op-e2e/e2eutils/retryproxy/proxy.go new file mode 100644 index 0000000000000..d36b9e29d9d47 --- /dev/null +++ b/op-e2e/e2eutils/retryproxy/proxy.go @@ -0,0 +1,176 @@ +package retryproxy + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "time" + + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum/go-ethereum/log" +) + +type jsonRPCReq struct { + Method string `json:"method"` +} + +var copyHeaders = []string{ + "Content-Type", +} + +type RetryProxy struct { + lgr log.Logger + upstream string + client *http.Client + strategy retry.Strategy + maxRetries int + srv *http.Server + listenPort int +} + +type Option func(*RetryProxy) + +func New(lgr log.Logger, upstream string, opts ...Option) *RetryProxy { + strategy := &retry.ExponentialStrategy{ + Min: 250 * time.Millisecond, + Max: 5 * time.Second, + MaxJitter: 250 * time.Millisecond, + } + + prox := &RetryProxy{ + lgr: lgr.New("module", "retryproxy"), + upstream: upstream, + client: &http.Client{}, + strategy: strategy, + maxRetries: 5, + } + + for _, opt := range opts { + opt(prox) + } + + return prox +} + +func (p *RetryProxy) Start() error { + errC := make(chan error, 1) + + go func() { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + errC <- fmt.Errorf("failed to listen: %w", err) + } + + p.listenPort = ln.Addr().(*net.TCPAddr).Port + + p.srv = &http.Server{ + Addr: "127.0.0.1:0", + Handler: p, + } + errC <- p.srv.Serve(ln) + }() + + timer := time.NewTimer(100 * time.Millisecond) + select { + case err := <-errC: + return fmt.Errorf("failed to start server: %w", err) + case <-timer.C: + p.lgr.Info("server started", "port", p.listenPort) + return nil + } +} + +func (p *RetryProxy) Stop() error { + if p.srv == nil { + return nil + } + + return p.srv.Shutdown(context.Background()) +} + +func (p *RetryProxy) Endpoint() string { + return fmt.Sprintf("http://127.0.0.1:%d", p.listenPort) +} + +func (p *RetryProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + defer r.Body.Close() + reqBody, err := io.ReadAll(r.Body) + if err != nil { + p.lgr.Error("failed to read request body", "err", err) + http.Error(w, "failed to read request body", http.StatusInternalServerError) + return + } + + //nolint:bodyClose + res, resBody, err := retry.Do2(r.Context(), p.maxRetries, p.strategy, func() (*http.Response, []byte, error) { + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + defer cancel() + res, err := p.doProxyReq(ctx, reqBody) + if err != nil { + p.lgr.Warn("failed to proxy request", "err", err) + return nil, nil, err + } + + defer res.Body.Close() + resBody, err := io.ReadAll(res.Body) + if err != nil { + p.lgr.Warn("failed to read response body", "err", err) + return nil, nil, err + } + + return res, resBody, nil + }) + if err != nil { + p.lgr.Error("permanently failed to proxy request", "err", err) + http.Error(w, "failed to proxy request", http.StatusBadGateway) + return + } + + for _, h := range copyHeaders { + w.Header().Set(h, res.Header.Get(h)) + } + + w.WriteHeader(http.StatusOK) + if _, err := io.Copy(w, bytes.NewReader(resBody)); err != nil { + p.lgr.Error("failed to copy response", "err", err) + http.Error(w, "failed to copy response", http.StatusInternalServerError) + return + } + + var jReq jsonRPCReq + if err := json.Unmarshal(reqBody, &jReq); err != nil { + p.lgr.Warn("failed to unmarshal request", "err", err) + return + } + + p.lgr.Debug("proxied request", "method", jReq.Method, "dur", time.Since(start)) +} + +func (p *RetryProxy) doProxyReq(ctx context.Context, body []byte) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.upstream, bytes.NewReader(body)) + if err != nil { + panic(fmt.Errorf("failed to create request: %w", err)) + } + res, err := p.client.Do(req) + if err != nil { + p.lgr.Warn("failed to proxy request", "err", err) + return nil, err + } + status := res.StatusCode + if status != 200 { + p.lgr.Warn("unexpected status code", "status", status) + return nil, fmt.Errorf("unexpected status code: %d", status) + } + return res, nil +} diff --git a/op-e2e/e2eutils/secrets.go b/op-e2e/e2eutils/secrets.go index cd4c91e1e09e5..9cee2f25c4c1f 100644 --- a/op-e2e/e2eutils/secrets.go +++ b/op-e2e/e2eutils/secrets.go @@ -19,8 +19,8 @@ const defaultHDPathPrefix = "m/44'/60'/0'/0/" // If these values are changed, it is subject to breaking tests. They // must be in sync with the values in the DeployConfig used to create the system. var DefaultMnemonicConfig = &MnemonicConfig{ - Mnemonic: "test test test test test test test test test test test junk", - CliqueSigner: "m/44'/60'/0'/0/0", + Mnemonic: "test test test test test test test test test test test junk", + // Note: "m/44'/60'/0'/0/0" is a legacy mnemonic path, used for the L1 clique signer. Proposer: "m/44'/60'/0'/0/1", Batcher: "m/44'/60'/0'/0/2", Deployer: "m/44'/60'/0'/0/3", @@ -36,9 +36,8 @@ var DefaultMnemonicConfig = &MnemonicConfig{ type MnemonicConfig struct { Mnemonic string - CliqueSigner string - Deployer string - SysCfgOwner string + Deployer string + SysCfgOwner string // rollup actors Proposer string @@ -66,10 +65,6 @@ func (m *MnemonicConfig) Secrets() (*Secrets, error) { if err != nil { return nil, err } - cliqueSigner, err := wallet.PrivateKey(account(m.CliqueSigner)) - if err != nil { - return nil, err - } sysCfgOwner, err := wallet.PrivateKey(account(m.SysCfgOwner)) if err != nil { return nil, err @@ -102,7 +97,6 @@ func (m *MnemonicConfig) Secrets() (*Secrets, error) { return &Secrets{ Deployer: deployer, SysCfgOwner: sysCfgOwner, - CliqueSigner: cliqueSigner, Proposer: proposer, Batcher: batcher, SequencerP2P: sequencerP2P, @@ -115,9 +109,8 @@ func (m *MnemonicConfig) Secrets() (*Secrets, error) { // Secrets bundles secp256k1 private keys for all common rollup actors for testing purposes. type Secrets struct { - Deployer *ecdsa.PrivateKey - CliqueSigner *ecdsa.PrivateKey - SysCfgOwner *ecdsa.PrivateKey + Deployer *ecdsa.PrivateKey + SysCfgOwner *ecdsa.PrivateKey // rollup actors Proposer *ecdsa.PrivateKey @@ -138,7 +131,6 @@ type Secrets struct { func (s *Secrets) Addresses() *Addresses { return &Addresses{ Deployer: crypto.PubkeyToAddress(s.Deployer.PublicKey), - CliqueSigner: crypto.PubkeyToAddress(s.CliqueSigner.PublicKey), SysCfgOwner: crypto.PubkeyToAddress(s.SysCfgOwner.PublicKey), Proposer: crypto.PubkeyToAddress(s.Proposer.PublicKey), Batcher: crypto.PubkeyToAddress(s.Batcher.PublicKey), @@ -151,9 +143,8 @@ func (s *Secrets) Addresses() *Addresses { // Addresses bundles the addresses for all common rollup addresses for testing purposes. type Addresses struct { - Deployer common.Address - CliqueSigner common.Address - SysCfgOwner common.Address + Deployer common.Address + SysCfgOwner common.Address // rollup actors Proposer common.Address @@ -169,7 +160,6 @@ type Addresses struct { func (a *Addresses) All() []common.Address { return []common.Address{ a.Deployer, - a.CliqueSigner, a.SysCfgOwner, a.Proposer, a.Batcher, diff --git a/op-e2e/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index 57c7c845672d4..dbc9ecea25b9c 100644 --- a/op-e2e/e2eutils/setup.go +++ b/op-e2e/e2eutils/setup.go @@ -106,6 +106,22 @@ func Ether(v uint64) *big.Int { return new(big.Int).Mul(new(big.Int).SetUint64(v), etherScalar) } +func GetL2AllocsMode(dc *genesis.DeployConfig, t uint64) genesis.L2AllocsMode { + if fork := dc.HoloceneTime(t); fork != nil && *fork <= 0 { + return genesis.L2AllocsHolocene + } + if fork := dc.GraniteTime(t); fork != nil && *fork <= 0 { + return genesis.L2AllocsGranite + } + if fork := dc.FjordTime(t); fork != nil && *fork <= 0 { + return genesis.L2AllocsFjord + } + if fork := dc.EcotoneTime(t); fork != nil && *fork <= 0 { + return genesis.L2AllocsEcotone + } + return genesis.L2AllocsDelta +} + // Setup computes the testing setup configurations from deployment configuration and optional allocation parameters. func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *SetupData { deployConf := deployParams.DeployConfig.Copy() @@ -135,11 +151,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * l1Block := l1Genesis.ToBlock() - var allocsMode genesis.L2AllocsMode - allocsMode = genesis.L2AllocsDelta - if ecotoneTime := deployConf.EcotoneTime(l1Block.Time()); ecotoneTime != nil && *ecotoneTime == 0 { - allocsMode = genesis.L2AllocsEcotone - } + allocsMode := GetL2AllocsMode(deployConf, l1Block.Time()) l2Allocs := config.L2Allocs(deployParams.AllocType, allocsMode) l2Genesis, err := genesis.BuildL2Genesis(deployConf, l2Allocs, l1Block.Header()) require.NoError(t, err, "failed to create l2 genesis") @@ -192,6 +204,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * EcotoneTime: deployConf.EcotoneTime(uint64(deployConf.L1GenesisBlockTimestamp)), FjordTime: deployConf.FjordTime(uint64(deployConf.L1GenesisBlockTimestamp)), GraniteTime: deployConf.GraniteTime(uint64(deployConf.L1GenesisBlockTimestamp)), + HoloceneTime: deployConf.HoloceneTime(uint64(deployConf.L1GenesisBlockTimestamp)), InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)), AltDAConfig: pcfg, } @@ -222,7 +235,8 @@ func SystemConfigFromDeployConfig(deployConfig *genesis.DeployConfig) eth.System } func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) { - isGranite := os.Getenv("OP_E2E_USE_GRANITE") == "true" + isHolocene := os.Getenv("OP_E2E_USE_HOLOCENE") == "true" + isGranite := isHolocene || os.Getenv("OP_E2E_USE_GRANITE") == "true" isFjord := isGranite || os.Getenv("OP_E2E_USE_FJORD") == "true" isEcotone := isFjord || os.Getenv("OP_E2E_USE_ECOTONE") == "true" isDelta := isEcotone || os.Getenv("OP_E2E_USE_DELTA") == "true" @@ -238,6 +252,9 @@ func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) { if isGranite { deployConfig.L2GenesisGraniteTimeOffset = new(hexutil.Uint64) } + if isHolocene { + deployConfig.L2GenesisHoloceneTimeOffset = new(hexutil.Uint64) + } // Canyon and lower is activated by default deployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64) deployConfig.L2GenesisRegolithTimeOffset = new(hexutil.Uint64) diff --git a/op-e2e/e2eutils/wait/waits.go b/op-e2e/e2eutils/wait/waits.go index 3c0e29466cea3..9b03e0bba7f9a 100644 --- a/op-e2e/e2eutils/wait/waits.go +++ b/op-e2e/e2eutils/wait/waits.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -164,3 +166,27 @@ func AndGet[T interface{}](ctx context.Context, pollRate time.Duration, get func } } } + +func ForNodeUp(ctx context.Context, client *ethclient.Client, lgr log.Logger) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + // Create a new context deliberately. The shorter timeout is used to detect + // potential I/O timeouts on the RPC so we can retry. + callCtx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + _, err := client.BlockNumber(callCtx) + cancel() + if err == nil { + lgr.Info("node is up") + return nil + } + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, os.ErrDeadlineExceeded) { + lgr.Info("timeout waiting for node come up, trying again") + continue + } + return err + } + } +} diff --git a/op-e2e/faultproofs/cannon_benchmark_test.go b/op-e2e/faultproofs/cannon_benchmark_test.go index 8a2592be59e77..9c05b9e9e1bd2 100644 --- a/op-e2e/faultproofs/cannon_benchmark_test.go +++ b/op-e2e/faultproofs/cannon_benchmark_test.go @@ -49,9 +49,6 @@ func testBenchmarkCannonFPP(t *testing.T, allocType config.AllocType) { cfg := e2esys.DefaultSystemConfig(t, e2esys.WithAllocType(allocType)) // We don't need a verifier - just the sequencer is enough delete(cfg.Nodes, "verifier") - // Use a small sequencer window size to avoid test timeout while waiting for empty blocks - // But not too small to ensure that our claim and subsequent state change is published - cfg.DeployConfig.SequencerWindowSize = 16 minTs := hexutil.Uint64(0) cfg.DeployConfig.L2GenesisDeltaTimeOffset = &minTs cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &minTs diff --git a/op-e2e/faultproofs/output_alphabet_test.go b/op-e2e/faultproofs/output_alphabet_test.go index 9255214ff39b7..258066305ef3e 100644 --- a/op-e2e/faultproofs/output_alphabet_test.go +++ b/op-e2e/faultproofs/output_alphabet_test.go @@ -167,7 +167,7 @@ func TestOutputAlphabetGame_ValidOutputRoot(t *testing.T) { } func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) { - op_e2e.InitParallel(t) + op_e2e.InitParallel(t, op_e2e.IsSlow) testCase := func(t *testing.T, isRootCorrect bool) { ctx := context.Background() diff --git a/op-e2e/faultproofs/output_cannon_test.go b/op-e2e/faultproofs/output_cannon_test.go index 80dea8bfb8ddd..0b0aea509feb4 100644 --- a/op-e2e/faultproofs/output_cannon_test.go +++ b/op-e2e/faultproofs/output_cannon_test.go @@ -252,9 +252,7 @@ func testOutputCannonDefendStep(t *testing.T, allocType config.AllocType) { sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) require.NoError(t, wait.ForNextBlock(ctx, l1Client)) - game.WaitForInactivity(ctx, 10, true) - game.LogGameData(ctx) - require.EqualValues(t, gameTypes.GameStatusChallengerWon, game.Status(ctx)) + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) } func TestOutputCannonStepWithLargePreimage_Standard(t *testing.T) { @@ -502,9 +500,7 @@ func testOutputCannonProposedOutputRootValid(t *testing.T, allocType config.Allo sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) require.NoError(t, wait.ForNextBlock(ctx, l1Client)) - game.WaitForInactivity(ctx, 10, true) - game.LogGameData(ctx) - require.EqualValues(t, gameTypes.GameStatusDefenderWon, game.Status(ctx)) + game.WaitForGameStatus(ctx, gameTypes.GameStatusDefenderWon) }) } } @@ -1003,9 +999,7 @@ func testOutputCannonHonestSafeTraceExtensionValidRoot(t *testing.T, allocType c sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) require.NoError(t, wait.ForNextBlock(ctx, l1Client)) - game.WaitForInactivity(ctx, 10, true) - game.LogGameData(ctx) - require.EqualValues(t, gameTypes.GameStatusDefenderWon, game.Status(ctx)) + game.WaitForGameStatus(ctx, gameTypes.GameStatusDefenderWon) } func TestOutputCannonHonestSafeTraceExtension_InvalidRoot_Standard(t *testing.T) { @@ -1052,9 +1046,7 @@ func testOutputCannonHonestSafeTraceExtensionInvalidRoot(t *testing.T, allocType sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx)) require.NoError(t, wait.ForNextBlock(ctx, l1Client)) - game.WaitForInactivity(ctx, 10, true) - game.LogGameData(ctx) - require.EqualValues(t, gameTypes.GameStatusChallengerWon, game.Status(ctx)) + game.WaitForGameStatus(ctx, gameTypes.GameStatusChallengerWon) } func TestAgreeFirstBlockWithOriginOf1_Standard(t *testing.T) { diff --git a/op-e2e/faultproofs/precompile_test.go b/op-e2e/faultproofs/precompile_test.go index 7fa37158fd16e..89b2ff92d3281 100644 --- a/op-e2e/faultproofs/precompile_test.go +++ b/op-e2e/faultproofs/precompile_test.go @@ -1,9 +1,12 @@ package faultproofs import ( + "bytes" "context" + "encoding/json" "math" "math/big" + "os/exec" "path/filepath" "testing" @@ -12,7 +15,6 @@ import ( "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" - "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -91,9 +93,6 @@ func testPrecompiles(t *testing.T, allocType e2e_config.AllocType) { cfg.AllocType = allocType // We don't need a verifier - just the sequencer is enough delete(cfg.Nodes, "verifier") - // Use a small sequencer window size to avoid test timeout while waiting for empty blocks - // But not too small to ensure that our claim and subsequent state change is published - cfg.DeployConfig.SequencerWindowSize = 16 sys, err := cfg.Start(t) require.Nil(t, err, "Error starting up system") @@ -199,9 +198,6 @@ func testGranitePrecompiles(t *testing.T, allocType e2e_config.AllocType) { cfg.AllocType = allocType // We don't need a verifier - just the sequencer is enough delete(cfg.Nodes, "verifier") - // Use a small sequencer window size to avoid test timeout while waiting for empty blocks - // But not too small to ensure that our claim and subsequent state change is published - cfg.DeployConfig.SequencerWindowSize = 16 sys, err := cfg.Start(t) require.Nil(t, err, "Error starting up system") @@ -273,18 +269,39 @@ func runCannon(t *testing.T, ctx context.Context, sys *e2esys.System, inputs uti dir := t.TempDir() proofsDir := filepath.Join(dir, "cannon-proofs") cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, rollupEndpoint, l2Endpoint, dir) + cfg.Cannon.L2Custom = true cannonOpts(&cfg) logger := testlog.Logger(t, log.LevelInfo).New("role", "cannon") - executor := vm.NewExecutor(logger, metrics.NoopMetrics.VmMetrics("cannon"), cfg.Cannon, vm.NewOpProgramServerExecutor(), cfg.CannonAbsolutePreState, inputs) + executor := vm.NewExecutor(logger, metrics.NoopMetrics.VmMetrics("cannon"), cfg.Cannon, vm.NewOpProgramServerExecutor(logger), cfg.CannonAbsolutePreState, inputs) t.Log("Running cannon") err := executor.DoGenerateProof(ctx, proofsDir, math.MaxUint, math.MaxUint, extraVmArgs...) require.NoError(t, err, "failed to generate proof") - state, err := versions.LoadStateFromFile(vm.FinalStatePath(proofsDir, cfg.Cannon.BinarySnapshots)) - require.NoError(t, err, "failed to parse state") - require.True(t, state.GetExited(), "cannon did not exit") - require.Zero(t, state.GetExitCode(), "cannon failed with exit code %d", state.GetExitCode()) - t.Logf("Completed in %d steps", state.GetStep()) + stdOut, _, err := runCmd(ctx, cfg.Cannon.VmBin, "witness", "--input", vm.FinalStatePath(proofsDir, cfg.Cannon.BinarySnapshots)) + require.NoError(t, err, "failed to run witness cmd") + type stateData struct { + Step uint64 `json:"step"` + ExitCode uint8 `json:"exitCode"` + Exited bool `json:"exited"` + } + var data stateData + err = json.Unmarshal([]byte(stdOut), &data) + require.NoError(t, err, "failed to parse state data") + require.True(t, data.Exited, "cannon did not exit") + require.Zero(t, data.ExitCode, "cannon failed with exit code %d", data.ExitCode) + t.Logf("Completed in %d steps", data.Step) +} + +func runCmd(ctx context.Context, binary string, args ...string) (stdOut string, stdErr string, err error) { + var outBuf bytes.Buffer + var errBuf bytes.Buffer + cmd := exec.CommandContext(ctx, binary, args...) + cmd.Stdout = &outBuf + cmd.Stderr = &errBuf + err = cmd.Run() + stdOut = outBuf.String() + stdErr = errBuf.String() + return } diff --git a/op-e2e/faultproofs/util.go b/op-e2e/faultproofs/util.go index 66b9be0060e53..04973071420f4 100644 --- a/op-e2e/faultproofs/util.go +++ b/op-e2e/faultproofs/util.go @@ -60,7 +60,7 @@ func StartFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*e2e cfg := e2esys.DefaultSystemConfig(t) delete(cfg.Nodes, "verifier") cfg.Nodes["sequencer"].SafeDBPath = t.TempDir() - cfg.DeployConfig.SequencerWindowSize = 4 + cfg.DeployConfig.SequencerWindowSize = 30 cfg.DeployConfig.FinalizationPeriodSeconds = 2 cfg.SupportL1TimeTravel = true // Disable proposer creating fast games automatically - required games are manually created diff --git a/op-e2e/interop/contracts/generate.sh b/op-e2e/interop/contracts/generate.sh deleted file mode 100755 index bba960153ec62..0000000000000 --- a/op-e2e/interop/contracts/generate.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -set -euo - -forge build - -cd build/emit.sol -cat EmitEvent.json | jq -r '.bytecode.object' > EmitEvent.bin -cat EmitEvent.json | jq '.abi' > EmitEvent.abi -cd ../.. - -abigen --abi ./build/emit.sol/EmitEvent.abi --bin ./build/emit.sol/EmitEvent.bin --pkg emit --out ./emit.go diff --git a/op-e2e/interop/interop_test.go b/op-e2e/interop/interop_test.go index 0d593673eccea..81f09c125aa4c 100644 --- a/op-e2e/interop/interop_test.go +++ b/op-e2e/interop/interop_test.go @@ -2,24 +2,31 @@ package interop import ( "context" - "fmt" "math/big" + "sync" "testing" "time" + "github.com/ethereum-optimism/optimism/op-service/dial" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/op-chain-ops/interopgen" "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + gethCore "github.com/ethereum/go-ethereum/core" + gethTypes "github.com/ethereum/go-ethereum/core/types" ) -// TestInteropTrivial tests a simple interop scenario -// Chains A and B exist, but no messages are sent between them -// and in fact no event-logs are emitted by either chain at all. -// A transaction is sent from Alice to Bob on Chain A. -// The balance of Bob on Chain A is checked before and after the tx. -// The balance of Bob on Chain B is checked after the tx. -func TestInteropTrivial(t *testing.T) { +// setupAndRun is a helper function that sets up a SuperSystem +// which contains two L2 Chains, and two users on each chain. +func setupAndRun(t *testing.T, config SuperSystemConfig, fn func(*testing.T, SuperSystem)) { recipe := interopgen.InteropDevRecipe{ L1ChainID: 900100, L2ChainIDs: []uint64{900200, 900201}, @@ -32,69 +39,289 @@ func TestInteropTrivial(t *testing.T) { // create a super system from the recipe // and get the L2 IDs for use in the test - s2 := NewSuperSystem(t, &recipe, worldResources) - ids := s2.L2IDs() - - // chainA is the first L2 chain - chainA := ids[0] - // chainB is the second L2 chain - chainB := ids[1] + s2 := NewSuperSystem(t, &recipe, worldResources, config) // create two users on all L2 chains s2.AddUser("Alice") s2.AddUser("Bob") - bobAddr := s2.Address(chainA, "Bob") - - // check the balance of Bob - clientA := s2.L2GethClient(chainA) - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - bobBalance, err := clientA.BalanceAt(ctx, bobAddr, nil) - require.NoError(t, err) - expectedBalance, _ := big.NewInt(0).SetString("10000000000000000000000000", 10) - require.Equal(t, expectedBalance, bobBalance) - - // send a tx from Alice to Bob - s2.SendL2Tx( - chainA, - "Alice", - func(l2Opts *helpers.TxOpts) { - l2Opts.ToAddr = &bobAddr - l2Opts.Value = big.NewInt(1000000) - l2Opts.GasFeeCap = big.NewInt(1_000_000_000) - l2Opts.GasTipCap = big.NewInt(1_000_000_000) - }, - ) - - // check the balance of Bob after the tx - ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - bobBalance, err = clientA.BalanceAt(ctx, bobAddr, nil) - require.NoError(t, err) - expectedBalance, _ = big.NewInt(0).SetString("10000000000000000001000000", 10) - require.Equal(t, expectedBalance, bobBalance) - - // check that the balance of Bob on ChainB hasn't changed - bobAddrB := s2.Address(chainB, "Bob") - clientB := s2.L2GethClient(chainB) - ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - bobBalance, err = clientB.BalanceAt(ctx, bobAddrB, nil) - require.NoError(t, err) - expectedBalance, _ = big.NewInt(0).SetString("10000000000000000000000000", 10) - require.Equal(t, expectedBalance, bobBalance) - - s2.DeployEmitterContract(chainA, "Alice") - rec := s2.EmitData(chainA, "Alice", "0x1234567890abcdef") - - fmt.Println("Result of emitting event:", rec) - - s2.DeployEmitterContract(chainB, "Alice") - rec = s2.EmitData(chainB, "Alice", "0x1234567890abcdef") - - fmt.Println("Result of emitting event:", rec) - - time.Sleep(60 * time.Second) + // run the test + fn(t, s2) +} + +// TestInterop_IsolatedChains tests a simple interop scenario +// Chains A and B exist, but no messages are sent between them +// a transaction is sent from Alice to Bob on Chain A, +// and only Chain A is affected. +func TestInterop_IsolatedChains(t *testing.T) { + test := func(t *testing.T, s2 SuperSystem) { + ids := s2.L2IDs() + chainA := ids[0] + chainB := ids[1] + + // check the balance of Bob + bobAddr := s2.Address(chainA, "Bob") + clientA := s2.L2GethClient(chainA) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + bobBalance, err := clientA.BalanceAt(ctx, bobAddr, nil) + require.NoError(t, err) + expectedBalance, _ := big.NewInt(0).SetString("10000000000000000000000000", 10) + require.Equal(t, expectedBalance, bobBalance) + + // send a tx from Alice to Bob + s2.SendL2Tx( + chainA, + "Alice", + func(l2Opts *helpers.TxOpts) { + l2Opts.ToAddr = &bobAddr + l2Opts.Value = big.NewInt(1000000) + l2Opts.GasFeeCap = big.NewInt(1_000_000_000) + l2Opts.GasTipCap = big.NewInt(1_000_000_000) + }, + ) + + // check the balance of Bob after the tx + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + bobBalance, err = clientA.BalanceAt(ctx, bobAddr, nil) + require.NoError(t, err) + expectedBalance, _ = big.NewInt(0).SetString("10000000000000000001000000", 10) + require.Equal(t, expectedBalance, bobBalance) + + // check that the balance of Bob on ChainB hasn't changed + bobAddrB := s2.Address(chainB, "Bob") + clientB := s2.L2GethClient(chainB) + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + bobBalance, err = clientB.BalanceAt(ctx, bobAddrB, nil) + require.NoError(t, err) + expectedBalance, _ = big.NewInt(0).SetString("10000000000000000000000000", 10) + require.Equal(t, expectedBalance, bobBalance) + } + config := SuperSystemConfig{ + mempoolFiltering: false, + } + setupAndRun(t, config, test) +} + +// TestInterop_EmitLogs tests a simple interop scenario +// Chains A and B exist, but no messages are sent between them. +// A contract is deployed on each chain, and logs are emitted repeatedly. +func TestInterop_EmitLogs(t *testing.T) { + test := func(t *testing.T, s2 SuperSystem) { + ids := s2.L2IDs() + chainA := ids[0] + chainB := ids[1] + EmitterA := s2.DeployEmitterContract(chainA, "Alice") + EmitterB := s2.DeployEmitterContract(chainB, "Alice") + payload1 := "SUPER JACKPOT!" + numEmits := 10 + // emit logs on both chains in parallel + var emitParallel sync.WaitGroup + emitOn := func(chainID string) { + for i := 0; i < numEmits; i++ { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + s2.EmitData(ctx, chainID, "Alice", payload1) + cancel() + } + emitParallel.Done() + } + emitParallel.Add(2) + go emitOn(chainA) + go emitOn(chainB) + emitParallel.Wait() + + clientA := s2.L2GethClient(chainA) + clientB := s2.L2GethClient(chainB) + // check that the logs are emitted on chain A + qA := ethereum.FilterQuery{ + Addresses: []common.Address{EmitterA}, + } + logsA, err := clientA.FilterLogs(context.Background(), qA) + require.NoError(t, err) + require.Len(t, logsA, numEmits) + + // check that the logs are emitted on chain B + qB := ethereum.FilterQuery{ + Addresses: []common.Address{EmitterB}, + } + logsB, err := clientB.FilterLogs(context.Background(), qB) + require.NoError(t, err) + require.Len(t, logsB, numEmits) + + // wait for cross-safety to settle + // I've tried 30s but not all logs are cross-safe by then + time.Sleep(60 * time.Second) + + supervisor := s2.SupervisorClient() + + // requireMessage checks the safety level of a log against the supervisor + // it also checks that the error is as expected + requireMessage := func(chainID string, log gethTypes.Log, expectedSafety types.SafetyLevel, expectedError error) { + client := s2.L2GethClient(chainID) + // construct the expected hash of the log's payload + // (topics concatenated with data) + msgPayload := make([]byte, 0) + for _, topic := range log.Topics { + msgPayload = append(msgPayload, topic.Bytes()...) + } + msgPayload = append(msgPayload, log.Data...) + expectedHash := common.BytesToHash(crypto.Keccak256(msgPayload)) + + // get block for the log (for timestamp) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + block, err := client.BlockByHash(ctx, log.BlockHash) + require.NoError(t, err) + + // make an identifier out of the sample log + identifier := types.Identifier{ + Origin: log.Address, + BlockNumber: log.BlockNumber, + LogIndex: uint32(log.Index), + Timestamp: block.Time(), + ChainID: types.ChainIDFromBig(s2.ChainID(chainID)), + } + + safety, err := supervisor.CheckMessage(context.Background(), + identifier, + expectedHash, + ) + require.ErrorIs(t, err, expectedError) + // the supervisor could progress the safety level more quickly than we expect, + // which is why we check for a minimum safety level + require.True(t, safety.AtLeastAsSafe(expectedSafety), "log: %v should be at least %s, but is %s", log, expectedSafety.String(), safety.String()) + } + // all logs should be cross-safe + for _, log := range logsA { + requireMessage(chainA, log, types.CrossSafe, nil) + } + for _, log := range logsB { + requireMessage(chainB, log, types.CrossSafe, nil) + } + } + config := SuperSystemConfig{ + mempoolFiltering: false, + } + setupAndRun(t, config, test) +} + +func TestInteropBlockBuilding(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + oplog.SetGlobalLogHandler(logger.Handler()) + + test := func(t *testing.T, s2 SuperSystem) { + ids := s2.L2IDs() + chainA := ids[0] + chainB := ids[1] + // We will initiate on chain A, and execute on chain B + s2.DeployEmitterContract(chainA, "Alice") + + // Add chain A as dependency to chain B, + // such that we can execute a message on B that was initiated on A. + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + depRec := s2.AddDependency(ctx, chainB, s2.ChainID(chainA)) + cancel() + t.Logf("Dependency set in L1 block %d", depRec.BlockNumber) + + rollupClA, err := dial.DialRollupClientWithTimeout(context.Background(), time.Second*15, logger, s2.OpNode(chainA).UserRPC().RPC()) + require.NoError(t, err) + + // Now wait for the dependency to be visible in the L2 (receipt needs to be picked up) + require.Eventually(t, func() bool { + status, err := rollupClA.SyncStatus(context.Background()) + require.NoError(t, err) + return status.CrossUnsafeL2.L1Origin.Number >= depRec.BlockNumber.Uint64() + }, time.Second*30, time.Second, "wait for L1 origin to match dependency L1 block") + t.Log("Dependency information has been processed in L2 block") + + // emit log on chain A + ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) + emitRec := s2.EmitData(ctx, chainA, "Alice", "hello world") + cancel() + t.Logf("Emitted a log event in block %d", emitRec.BlockNumber.Uint64()) + + // Wait for initiating side to become cross-unsafe + require.Eventually(t, func() bool { + status, err := rollupClA.SyncStatus(context.Background()) + require.NoError(t, err) + return status.CrossUnsafeL2.Number >= emitRec.BlockNumber.Uint64() + }, time.Second*60, time.Second, "wait for emitted data to become cross-unsafe") + t.Logf("Reached cross-unsafe block %d", emitRec.BlockNumber.Uint64()) + + // Identify the log + require.Len(t, emitRec.Logs, 1) + ev := emitRec.Logs[0] + ethCl := s2.L2GethClient(chainA) + header, err := ethCl.HeaderByHash(context.Background(), emitRec.BlockHash) + require.NoError(t, err) + identifier := types.Identifier{ + Origin: ev.Address, + BlockNumber: ev.BlockNumber, + LogIndex: uint32(ev.Index), + Timestamp: header.Time, + ChainID: types.ChainIDFromBig(s2.ChainID(chainA)), + } + + msgPayload := types.LogToMessagePayload(ev) + payloadHash := crypto.Keccak256Hash(msgPayload) + logHash := types.PayloadHashToLogHash(payloadHash, identifier.Origin) + t.Logf("expected payload hash: %s", payloadHash) + t.Logf("expected log hash: %s", logHash) + + invalidPayload := []byte("test invalid message") + invalidPayloadHash := crypto.Keccak256Hash(invalidPayload) + invalidLogHash := types.PayloadHashToLogHash(invalidPayloadHash, identifier.Origin) + t.Logf("invalid payload hash: %s", invalidPayloadHash) + t.Logf("invalid log hash: %s", invalidLogHash) + + // submit executing txs on B + + t.Log("Testing invalid message") + { + bobAddr := s2.Address(chainA, "Bob") // direct it to a random account without code + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + // Send an executing message, but with different payload. + if s2.(*interopE2ESystem).config.mempoolFiltering { + // We expect the traqnsaction to be filtered out by the mempool if mempool filtering is enabled. + // ExecuteMessage the ErrTxFilteredOut error is checked when sending the tx. + _, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, invalidPayload, gethCore.ErrTxFilteredOut) + require.ErrorContains(t, err, gethCore.ErrTxFilteredOut.Error()) + } else { + // We expect the miner to be unable to include this tx, and confirmation to thus time out, if mempool filtering is disabled. + _, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, invalidPayload, nil) + require.ErrorIs(t, err, ctx.Err()) + require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded) + } + } + + t.Log("Testing valid message now") + { + bobAddr := s2.Address(chainA, "Bob") // direct it to a random account without code + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + // Send an executing message with the correct identifier / payload + rec, err := s2.ExecuteMessage(ctx, chainB, "Alice", identifier, bobAddr, msgPayload, nil) + require.NoError(t, err, "expecting tx to be confirmed") + t.Logf("confirmed executing msg in block %s", rec.BlockNumber) + } + t.Log("Done") + } + + t.Run("without mempool filtering", func(t *testing.T) { + config := SuperSystemConfig{ + mempoolFiltering: false, + } + setupAndRun(t, config, test) + }) + t.Run("with mempool filtering", func(t *testing.T) { + config := SuperSystemConfig{ + mempoolFiltering: true, + } + // run again with mempool filtering to observe the behavior of the mempool filter + setupAndRun(t, config, test) + }) } diff --git a/op-e2e/interop/supersystem.go b/op-e2e/interop/supersystem.go index 3630b87dc8963..8fc663f377a0b 100644 --- a/op-e2e/interop/supersystem.go +++ b/op-e2e/interop/supersystem.go @@ -7,13 +7,13 @@ import ( "os" "path" "path/filepath" + "sort" "testing" "time" - emit "github.com/ethereum-optimism/optimism/op-e2e/interop/contracts" - "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" + "github.com/ethereum/go-ethereum/eth/ethconfig" + gn "github.com/ethereum/go-ethereum/node" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -25,16 +25,24 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/interop/contracts/bindings/emit" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/interop/contracts/bindings/inbox" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/interop/contracts/bindings/systemconfig" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/interopgen" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/opnode" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/services" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/setuputils" + "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" "github.com/ethereum-optimism/optimism/op-node/node" "github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" @@ -53,6 +61,8 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" supervisorConfig "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + supervisortypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) // SuperSystem is an interface for the system (collection of connected resources) @@ -78,8 +88,10 @@ type SuperSystem interface { L2GethClient(network string) *ethclient.Client // get the secret for a network and role L2OperatorKey(network string, role devkeys.ChainOperatorRole) ecdsa.PrivateKey - // get the list of network IDs + // get the list of network IDs as key-strings L2IDs() []string + // get the chain ID for a network + ChainID(network string) *big.Int // register a username to an account on all L2s AddUser(username string) // get the user key for a user on an L2 @@ -91,14 +103,29 @@ type SuperSystem interface { // Deploy the Emitter Contract, which emits Event Logs DeployEmitterContract(network string, username string) common.Address // Use the Emitter Contract to emit an Event Log - EmitData(network string, username string, data string) *types.Receipt + EmitData(ctx context.Context, network string, username string, data string) *types.Receipt + // AddDependency adds a dependency (by chain ID) to the given chain + AddDependency(ctx context.Context, network string, dep *big.Int) *types.Receipt + // ExecuteMessage calls the CrossL2Inbox executeMessage function + ExecuteMessage( + ctx context.Context, + id string, + sender string, + msgIdentifier supervisortypes.Identifier, + target common.Address, + message []byte, + expectedError error, + ) (*types.Receipt, error) // Access a contract on a network by name Contract(network string, contractName string) interface{} } +type SuperSystemConfig struct { + mempoolFiltering bool +} // NewSuperSystem creates a new SuperSystem from a recipe. It creates an interopE2ESystem. -func NewSuperSystem(t *testing.T, recipe *interopgen.InteropDevRecipe, w worldResourcePaths) SuperSystem { - s2 := &interopE2ESystem{recipe: recipe} +func NewSuperSystem(t *testing.T, recipe *interopgen.InteropDevRecipe, w worldResourcePaths, config SuperSystemConfig) SuperSystem { + s2 := &interopE2ESystem{recipe: recipe, config: &config} s2.prepare(t, w) return s2 } @@ -117,9 +144,11 @@ type interopE2ESystem struct { beacon *fakebeacon.FakeBeacon l1 *geth.GethInstance l2s map[string]l2Set + l1GethClient *ethclient.Client l2GethClients map[string]*ethclient.Client supervisor *supervisor.SupervisorService superClient *sources.SupervisorClient + config *SuperSystemConfig } // l2Set is a set of resources for an L2 chain @@ -196,6 +225,7 @@ func (s *interopE2ESystem) prepareL1() (*fakebeacon.FakeBeacon, *geth.GethInstan require.NoError(s.t, err) require.NoError(s.t, l1Geth.Node.Start()) s.t.Cleanup(func() { + s.t.Logf("Closing L1 geth") _ = l1Geth.Close() }) return bcn, l1Geth @@ -235,11 +265,18 @@ func (s *interopE2ESystem) newOperatorKeysForL2(l2Out *interopgen.L2Output) map[ func (s *interopE2ESystem) newGethForL2(id string, l2Out *interopgen.L2Output) *geth.GethInstance { jwtPath := writeDefaultJWT(s.t) name := "l2-" + id - l2Geth, err := geth.InitL2(name, l2Out.Genesis, jwtPath) + l2Geth, err := geth.InitL2(name, l2Out.Genesis, jwtPath, + func(ethCfg *ethconfig.Config, nodeCfg *gn.Config) error { + ethCfg.InteropMessageRPC = s.supervisor.RPC() + ethCfg.InteropMempoolFiltering = s.config.mempoolFiltering + return nil + }) require.NoError(s.t, err) require.NoError(s.t, l2Geth.Node.Start()) s.t.Cleanup(func() { - _ = l2Geth.Close() + s.t.Logf("Closing L2 geth of chain %s", id) + closeErr := l2Geth.Close() + s.t.Logf("Closed L2 geth of chain %s: %v", id, closeErr) }) return l2Geth } @@ -302,7 +339,9 @@ func (s *interopE2ESystem) newNodeForL2( s.t.Cleanup(func() { ctx, cancel := context.WithCancel(context.Background()) cancel() // force-quit + s.t.Logf("Closing op-node of chain %s", id) _ = opNode.Stop(ctx) + s.t.Logf("Closed op-node of chain %s", id) }) return opNode } @@ -386,7 +425,9 @@ func (s *interopE2ESystem) newBatcherForL2( s.t.Cleanup(func() { ctx, cancel := context.WithCancel(context.Background()) cancel() // force-quit + s.t.Logf("Closing batcher of chain %s", id) _ = batcher.Stop(ctx) + s.t.Logf("Closed batcher of chain %s", id) }) return batcher } @@ -414,11 +455,15 @@ func (s *interopE2ESystem) newL2(id string, l2Out *interopgen.L2Output) l2Set { } } +func (s *interopE2ESystem) ChainID(network string) *big.Int { + return s.l2s[network].chainID +} + // prepareSupervisor creates a new supervisor for the system func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService { // Be verbose with op-supervisor, it's in early test phase logger := testlog.Logger(s.t, log.LevelDebug).New("role", "supervisor") - cfg := supervisorConfig.Config{ + cfg := &supervisorConfig.Config{ MetricsConfig: metrics.CLIConfig{ Enabled: false, }, @@ -437,11 +482,25 @@ func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService { L2RPCs: []string{}, Datadir: path.Join(s.t.TempDir(), "supervisor"), } - for id := range s.l2s { - cfg.L2RPCs = append(cfg.L2RPCs, s.l2s[id].l2Geth.UserRPC().RPC()) + depSet := make(map[supervisortypes.ChainID]*depset.StaticConfigDependency) + + // Iterate over the L2 chain configs. The L2 nodes don't exist yet. + for _, l2Out := range s.worldOutput.L2s { + chainID := supervisortypes.ChainIDFromBig(l2Out.Genesis.Config.ChainID) + index, err := chainID.ToUInt32() + require.NoError(s.t, err) + depSet[chainID] = &depset.StaticConfigDependency{ + ChainIndex: supervisortypes.ChainIndex(index), + ActivationTime: 0, + HistoryMinTime: 0, + } } + stDepSet, err := depset.NewStaticConfigDependencySet(depSet) + require.NoError(s.t, err) + cfg.DependencySetSource = stDepSet + // Create the supervisor with the configuration - super, err := supervisor.SupervisorFromConfig(context.Background(), &cfg, logger) + super, err := supervisor.SupervisorFromConfig(context.Background(), cfg, logger) require.NoError(s.t, err) // Start the supervisor err = super.Start(context.Background()) @@ -449,7 +508,9 @@ func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService { s.t.Cleanup(func() { ctx, cancel := context.WithCancel(context.Background()) cancel() // force-quit - _ = super.Stop(ctx) + s.t.Logf("Closing supervisor") + closeErr := super.Stop(ctx) + s.t.Logf("Closed supervisor: %v", closeErr) }) return super } @@ -481,12 +542,23 @@ func (s *interopE2ESystem) prepare(t *testing.T, w worldResourcePaths) { s.beacon, s.l1 = s.prepareL1() s.l2s = s.prepareL2s() + s.prepareContracts() + // add the L2 RPCs to the supervisor now that the L2s are created ctx := context.Background() for _, l2 := range s.l2s { err := s.SupervisorClient().AddL2RPC(ctx, l2.l2Geth.UserRPC().RPC()) - require.NoError(s.t, err, "failed to add L2 RPC to supervisor", "error", err) + require.NoError(s.t, err, "failed to add L2 RPC to supervisor") } + + // Try to close the op-supervisor first + s.t.Cleanup(func() { + ctx, cancel := context.WithCancel(context.Background()) + cancel() // force-quit + s.t.Logf("Closing supervisor") + closeErr := s.supervisor.Stop(ctx) + s.t.Logf("Closed supervisor: %v", closeErr) + }) } // AddUser adds a user to the system by creating a user key for each L2. @@ -530,6 +602,44 @@ func (s *interopE2ESystem) prepareL2s() map[string]l2Set { return l2s } +// prepareContracts prepares contract-bindings for the L2s +func (s *interopE2ESystem) prepareContracts() { + // Add bindings to common contracts for each L2 + l1GethClient := s.L1GethClient() + for id, l2Dep := range s.worldDeployment.L2s { + { + contract, err := inbox.NewInbox(predeploys.CrossL2InboxAddr, s.L2GethClient(id)) + require.NoError(s.t, err) + s.l2s[id].contracts["inbox"] = contract + } + { + contract, err := systemconfig.NewSystemconfig(l2Dep.SystemConfigProxy, l1GethClient) + require.NoError(s.t, err) + s.l2s[id].contracts["systemconfig"] = contract + } + } +} + +func (s *interopE2ESystem) L1GethClient() *ethclient.Client { + if s.l1GethClient != nil { + return s.l1GethClient + } + rpcEndpoint := s.l1.UserRPC() + rpcCl := endpoint.DialRPC( + endpoint.PreferAnyRPC, + rpcEndpoint, + func(v string) *rpc.Client { + logger := testlog.Logger(s.t, log.LevelInfo) + cl, err := dial.DialRPCClientWithTimeout(context.Background(), 30*time.Second, logger, v) + require.NoError(s.t, err, "failed to dial L1 eth node instance") + return cl + }) + nodeClient := ethclient.NewClient(rpcCl) + // register the client so it can be reused + s.l1GethClient = nodeClient + return nodeClient +} + func (s *interopE2ESystem) L2GethClient(id string) *ethclient.Client { // guard: check if the client already exists and return it in that case nodeClient, ok := s.l2GethClients[id] @@ -589,6 +699,7 @@ func (s *interopE2ESystem) L2IDs() []string { for id := range s.l2s { ids = append(ids, id) } + sort.Strings(ids) return ids } @@ -616,6 +727,74 @@ func (s *interopE2ESystem) SendL2Tx( newApply) } +// ExecuteMessage calls the CrossL2Inbox executeMessage function +// it uses the L2's chain ID, username key, and geth client. +// expectedError represents the error returned by `ExecuteMessage` if it is expected. +// the returned err is related to `WaitMined` +func (s *interopE2ESystem) ExecuteMessage( + ctx context.Context, + id string, + sender string, + msgIdentifier supervisortypes.Identifier, + target common.Address, + message []byte, + expectedError error, +) (*types.Receipt, error) { + secret := s.UserKey(id, sender) + auth, err := bind.NewKeyedTransactorWithChainID(&secret, s.l2s[id].chainID) + + require.NoError(s.t, err) + + auth.GasLimit = uint64(3000_000) + auth.GasPrice = big.NewInt(20_000_000_000) + + contract := s.Contract(id, "inbox").(*inbox.Inbox) + identifier := inbox.Identifier{ + Origin: msgIdentifier.Origin, + BlockNumber: new(big.Int).SetUint64(msgIdentifier.BlockNumber), + LogIndex: new(big.Int).SetUint64(uint64(msgIdentifier.LogIndex)), + Timestamp: new(big.Int).SetUint64(msgIdentifier.Timestamp), + ChainId: msgIdentifier.ChainID.ToBig(), + } + tx, err := contract.InboxTransactor.ExecuteMessage(auth, identifier, target, message) + if expectedError != nil { + require.ErrorContains(s.t, err, expectedError.Error()) + return nil, err + } else { + require.NoError(s.t, err) + } + s.logger.Info("Executing message", "tx", tx.Hash(), "to", tx.To(), "target", target, "data", hexutil.Bytes(tx.Data())) + return bind.WaitMined(ctx, s.L2GethClient(id), tx) +} + +func (s *interopE2ESystem) AddDependency(ctx context.Context, id string, dep *big.Int) *types.Receipt { + // There is a note in OPContractsManagerInterop that the proxy-admin is used for now, + // even though it should be a separate dependency-set-manager address. + secret, err := s.hdWallet.Secret(devkeys.ChainOperatorKey{ + ChainID: s.l2s[id].chainID, + Role: devkeys.SystemConfigOwner, + }) + require.NoError(s.t, err) + + auth, err := bind.NewKeyedTransactorWithChainID(secret, s.worldOutput.L1.Genesis.Config.ChainID) + require.NoError(s.t, err) + + balance, err := s.l1GethClient.BalanceAt(ctx, crypto.PubkeyToAddress(secret.PublicKey), nil) + require.NoError(s.t, err) + require.False(s.t, balance.Sign() == 0, "system config owner needs a balance") + + auth.GasLimit = uint64(3000000) + auth.GasPrice = big.NewInt(20000000000) + + contract := s.Contract(id, "systemconfig").(*systemconfig.Systemconfig) + tx, err := contract.SystemconfigTransactor.AddDependency(auth, dep) + require.NoError(s.t, err) + + receipt, err := wait.ForReceiptOK(ctx, s.L1GethClient(), tx.Hash()) + require.NoError(s.t, err) + return receipt +} + func (s *interopE2ESystem) DeployEmitterContract( id string, sender string, @@ -634,6 +813,7 @@ func (s *interopE2ESystem) DeployEmitterContract( } func (s *interopE2ESystem) EmitData( + ctx context.Context, id string, sender string, data string, @@ -649,7 +829,7 @@ func (s *interopE2ESystem) EmitData( contract := s.Contract(id, "emitter").(*emit.Emit) tx, err := contract.EmitTransactor.EmitData(auth, []byte(data)) require.NoError(s.t, err) - receipt, err := bind.WaitMined(context.Background(), s.L2GethClient(id), tx) + receipt, err := bind.WaitMined(ctx, s.L2GethClient(id), tx) require.NoError(s.t, err) return receipt } diff --git a/op-e2e/opgeth/fastlz_test.go b/op-e2e/opgeth/fastlz_test.go index 5b03ca38eca3c..3b910d41f816d 100644 --- a/op-e2e/opgeth/fastlz_test.go +++ b/op-e2e/opgeth/fastlz_test.go @@ -173,7 +173,7 @@ func FuzzFjordCostFunction(f *testing.F) { l1FeeSolidity.Mul(l1FeeSolidity, feeScaled) l1FeeSolidity.Div(l1FeeSolidity, big.NewInt(1e12)) - costData := types.NewRollupCostData(fuzzedData) + costData := types.NewRollupCostData(fuzzedData, 0) l1FeeGeth := costFunc(costData, zeroTime) diff --git a/op-e2e/opgeth/op_geth.go b/op-e2e/opgeth/op_geth.go index 8785ea1a95519..ac0ea133d9c1b 100644 --- a/op-e2e/opgeth/op_geth.go +++ b/op-e2e/opgeth/op_geth.go @@ -59,19 +59,15 @@ func NewOpGeth(t testing.TB, ctx context.Context, cfg *e2esys.SystemConfig) (*Op l1Genesis, err := genesis.BuildL1DeveloperGenesis(cfg.DeployConfig, config.L1Allocs(config.AllocTypeStandard), config.L1Deployments(config.AllocTypeStandard)) require.NoError(t, err) l1Block := l1Genesis.ToBlock() - - var allocsMode genesis.L2AllocsMode - allocsMode = genesis.L2AllocsDelta - if graniteTime := cfg.DeployConfig.GraniteTime(l1Block.Time()); graniteTime != nil && *graniteTime <= 0 { - allocsMode = genesis.L2AllocsGranite - } else if fjordTime := cfg.DeployConfig.FjordTime(l1Block.Time()); fjordTime != nil && *fjordTime <= 0 { - allocsMode = genesis.L2AllocsFjord - } else if ecotoneTime := cfg.DeployConfig.EcotoneTime(l1Block.Time()); ecotoneTime != nil && *ecotoneTime <= 0 { - allocsMode = genesis.L2AllocsEcotone - } + allocsMode := e2eutils.GetL2AllocsMode(cfg.DeployConfig, l1Block.Time()) l2Allocs := config.L2Allocs(config.AllocTypeStandard, allocsMode) l2Genesis, err := genesis.BuildL2Genesis(cfg.DeployConfig, l2Allocs, l1Block.Header()) require.NoError(t, err) + if cfg.UseSoulGasToken { + l2Genesis.Config.Optimism.UseSoulGasToken = true + l2Genesis.Config.Optimism.IsSoulBackedByNative = cfg.IsSoulBackedByNative + } + l2GenesisBlock := l2Genesis.ToBlock() rollupGenesis := rollup.Genesis{ @@ -244,5 +240,9 @@ func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.Payloa Withdrawals: withdrawals, ParentBeaconBlockRoot: parentBeaconBlockRoot, } + if d.L2ChainConfig.IsHolocene(uint64(timestamp)) { + attrs.EIP1559Params = new(eth.Bytes8) + *attrs.EIP1559Params = d.SystemConfig.EIP1559Params + } return &attrs, nil } diff --git a/op-e2e/opgeth/op_geth_test.go b/op-e2e/opgeth/op_geth_test.go index 0f27e7b7f0473..1097c1218ad77 100644 --- a/op-e2e/opgeth/op_geth_test.go +++ b/op-e2e/opgeth/op_geth_test.go @@ -7,14 +7,11 @@ import ( "testing" "time" - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" - - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - "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/go-ethereum" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/txpool" @@ -22,8 +19,13 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/ethereum/go-ethereum/rpc" + + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + "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" ) var ( @@ -51,8 +53,9 @@ func TestMissingGasLimit(t *testing.T) { res, err := opGeth.StartBlockBuilding(ctx, attrs) require.Error(t, err) - require.ErrorIs(t, err, eth.InputError{}) - require.Equal(t, eth.InvalidPayloadAttributes, err.(eth.InputError).Code) + var rpcErr rpc.Error + require.ErrorAs(t, err, &rpcErr) + require.EqualValues(t, eth.InvalidPayloadAttributes, rpcErr.ErrorCode()) require.Nil(t, res) } diff --git a/op-e2e/sgt/helper.go b/op-e2e/sgt/helper.go new file mode 100644 index 0000000000000..d39dd4e436202 --- /dev/null +++ b/op-e2e/sgt/helper.go @@ -0,0 +1,92 @@ +package sgt + +import ( + "context" + "crypto/ecdsa" + "math/big" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +type SgtHelper struct { + T *testing.T + L2Client *ethclient.Client + SysCfg e2esys.SystemConfig + SgtContract *bindings.SoulGasToken + ChainID *big.Int + sys *e2esys.System +} + +func NewSgtHelper(t *testing.T, ctx context.Context, sys *e2esys.System) *SgtHelper { + // use sequencer's L2 client + client := sys.NodeClient(e2esys.RoleSeq) + chainID, err := client.ChainID(ctx) + require.NoError(t, err) + + sgtAddr := predeploys.SoulGasTokenAddr + sgtContract, err := bindings.NewSoulGasToken(sgtAddr, client) + require.NoError(t, err) + + return &SgtHelper{ + T: t, + L2Client: client, + SysCfg: sys.Cfg, + SgtContract: sgtContract, + ChainID: chainID, + sys: sys, + } +} + +func (s *SgtHelper) GetTestAccount(idx int) *ecdsa.PrivateKey { + return s.sys.TestAccount(idx) +} + +func (s *SgtHelper) depositSgtAndNativeFromGenesisAccountToAccount(t *testing.T, ctx context.Context, toAddr common.Address, sgtValue *big.Int, l2Value *big.Int) { + privKey := s.GetTestAccount(0) // Genesis Account with lots of native balances + // deposit some sgt and native tokens first + txOpts, err := bind.NewKeyedTransactorWithChainID(privKey, s.ChainID) + require.NoError(t, err) + txOpts.Value = sgtValue + sgtTx, err := s.SgtContract.BatchDepositForAll(txOpts, []common.Address{toAddr}, sgtValue) + require.NoError(t, err) + _, err = wait.ForReceiptOK(ctx, s.L2Client, sgtTx.Hash()) + require.NoError(t, err) + nativeTx, err := s.transferNativeToken(t, ctx, privKey, toAddr, l2Value) + require.NoError(t, err) + _, err = wait.ForReceiptOK(ctx, s.L2Client, nativeTx.Hash()) + require.NoError(t, err) +} + +func (s *SgtHelper) transferNativeToken(t *testing.T, ctx context.Context, sender *ecdsa.PrivateKey, toAddr common.Address, amount *big.Int) (*types.Transaction, error) { + chainID, err := s.L2Client.ChainID(ctx) + require.NoError(t, err) + gasFeeCap := big.NewInt(200) + gasTipCap := big.NewInt(10) + + nonce, err := s.L2Client.NonceAt(ctx, crypto.PubkeyToAddress(sender.PublicKey), nil) + require.NoError(t, err) + tx := types.MustSignNewTx(sender, types.LatestSignerForChainID(chainID), &types.DynamicFeeTx{ + ChainID: chainID, + Nonce: nonce, + To: &toAddr, + Value: amount, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Gas: 21000, + }) + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + err = s.L2Client.SendTransaction(ctx, tx) + return tx, err +} diff --git a/op-e2e/sgt/sgt_test.go b/op-e2e/sgt/sgt_test.go new file mode 100644 index 0000000000000..ad447a56e7323 --- /dev/null +++ b/op-e2e/sgt/sgt_test.go @@ -0,0 +1,369 @@ +package sgt + +import ( + "context" + "crypto/ecdsa" + "math/big" + "math/rand" + "testing" + + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-e2e/faultproofs" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum-optimism/optimism/op-service/testutils" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +var ( + seqVault = predeploys.SequencerFeeVaultAddr + baseVault = predeploys.BaseFeeVaultAddr + l1Vault = predeploys.L1FeeVaultAddr + dummyAddr = common.Address{0xff, 0xff} +) + +func TestSGTDepositFunctionSuccess(t *testing.T) { + op_e2e.InitParallel(t) + sys, _ := faultproofs.StartFaultDisputeSystem(t) + t.Cleanup(sys.Close) + ctx := context.Background() + + sgt := NewSgtHelper(t, ctx, sys) + depositSgtValue := big.NewInt(10000) + _, _, _ = setUpTestAccount(t, ctx, 0, sgt, depositSgtValue, big.NewInt(0)) +} + +// Diverse test scenarios to verify that the SoulGasToken(sgt) is utilized for gas payment firstly, +// unless there is insufficient sgt balance, in which case the native balance will be used instead. +func TestSGTAsGasPayment(t *testing.T) { + op_e2e.InitParallel(t) + sys, _ := faultproofs.StartFaultDisputeSystem(t) + t.Cleanup(sys.Close) + ctx := context.Background() + + sgt := NewSgtHelper(t, ctx, sys) + // 1. setup a test account and deposit specified amount of sgt tokens (`depositSgtValue`) and native tokens (`depositL2Value`) into it. + // 2. execute a token transfer tx with `txValue` to `dummyAddr` and validate that the gas payment behavior using sgt is as anticipated. + tests := []struct { + name string + action func(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) + }{ + { + name: "NativaGasPaymentWithoutSGTSuccess", + action: nativaGasPaymentWithoutSGTSuccess, + }, + { + name: "FullSGTGasPaymentWithoutNativeBalanceSuccess", + action: fullSGTGasPaymentWithoutNativeBalanceSuccess, + }, + { + name: "FullSGTGasPaymentWithNativeBalanceSuccess", + action: fullSGTGasPaymentWithNativeBalanceSuccess, + }, + { + name: "PartialSGTGasPaymentSuccess", + action: partialSGTGasPaymentSuccess, + }, + { + name: "FullSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess", + action: fullSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess, + }, + { + name: "PartialSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess", + action: partialSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess, + }, + { + name: "FullSGTInsufficientGasPaymentFail", + action: fullSGTInsufficientGasPaymentFail, + }, + { + name: "FullNativeInsufficientGasPaymentFail", + action: fullNativeInsufficientGasPaymentFail, + }, + { + name: "PartialSGTInsufficientGasPaymentFail", + action: partialSGTInsufficientGasPaymentFail, + }, + { + name: "FullSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail", + action: fullSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail, + }, + { + name: "PartialSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail", + action: partialSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail, + }, + } + + for index, tCase := range tests { + t.Run(tCase.name, func(t *testing.T) { + tCase.action(t, ctx, int64(index), sgt) + }) + } +} + +func setUpTestAccount(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper, depositSgtValue *big.Int, depositL2Value *big.Int) (*ecdsa.PrivateKey, common.Address, *big.Int) { + opts := &bind.CallOpts{Context: ctx} + rng := rand.New(rand.NewSource(index)) + testPrivKey := testutils.InsecureRandomKey(rng) + testAddr := crypto.PubkeyToAddress(testPrivKey.PublicKey) + + // check it's a fresh account + sgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, int64(0), sgtBalance.Int64()) + l2Balance, err := sgt.L2Client.BalanceAt(ctx, testAddr, nil) + require.NoError(t, err) + require.Equal(t, int64(0), l2Balance.Int64()) + + // deposit initial sgt and native(L2) balance to the test account + sgt.depositSgtAndNativeFromGenesisAccountToAccount(t, ctx, testAddr, depositSgtValue, depositL2Value) + // ensure that sgt and native balance of testAccount are correctly initialized + preSgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, depositSgtValue.Cmp(preSgtBalance), 0) + preL2Balance, err := sgt.L2Client.BalanceAt(ctx, testAddr, nil) + require.NoError(t, err) + require.Equal(t, depositL2Value.Cmp(preL2Balance), 0) + + return testPrivKey, testAddr, calcVaultBalance(t, ctx, sgt) +} + +// balance invariant check: preTotalBalance = postTotalBalance + gasCost + txValue +func invariantBalanceCheck(t *testing.T, ctx context.Context, sgt *SgtHelper, addr common.Address, gasCost *big.Int, txValue *big.Int, preSgtBalance *big.Int, preL2Balance *big.Int, postSgtBalance *big.Int) { + postL2Balance, err := sgt.L2Client.BalanceAt(ctx, addr, nil) + require.NoError(t, err) + preBalance := new(big.Int).Add(preSgtBalance, preL2Balance) + postBalance := new(big.Int).Add(postSgtBalance, gasCost) + postBalance = postBalance.Add(postBalance, txValue) + postBalance = postBalance.Add(postBalance, postL2Balance) + require.Equal(t, 0, preBalance.Cmp(postBalance)) +} + +func nativaGasPaymentWithoutSGTSuccess(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(0) + // 10000000000000 is a random chosen value that is far bigger than the gas cos (~1225000231000) of the following `transferNativeToken` tx + depositL2Value := big.NewInt(10000000000000) + txValue := big.NewInt(0) + testAccount, testAddr, vaultBalanceBefore := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + tx, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, sgt.L2Client, tx.Hash()) + require.NoError(t, err) + gasCost := calcGasFee(receipt) + vaultBalanceAfter := calcVaultBalance(t, ctx, sgt) + + // gasCost == vaultBalanceDiff check + require.Equal(t, new(big.Int).Sub(vaultBalanceAfter, vaultBalanceBefore).Cmp(gasCost), 0) + // post sgt balance check: it should be 0 + opts := &bind.CallOpts{Context: ctx} + postSgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, common.Big0.Cmp(postSgtBalance), 0) + // balance invariant check + invariantBalanceCheck(t, ctx, sgt, testAddr, gasCost, txValue, depositSgtValue, depositL2Value, postSgtBalance) +} + +func fullSGTGasPaymentWithoutNativeBalanceSuccess(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(10000000000000) + depositL2Value := big.NewInt(0) + txValue := big.NewInt(0) + testAccount, testAddr, vaultBalanceBefore := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + tx, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, sgt.L2Client, tx.Hash()) + require.NoError(t, err) + gasCost := calcGasFee(receipt) + vaultBalanceAfter := calcVaultBalance(t, ctx, sgt) + + // gasCost == vaultBalanceDiff check + require.Equal(t, new(big.Int).Sub(vaultBalanceAfter, vaultBalanceBefore).Cmp(gasCost), 0) + // post sgt balance check: sgt should be used as gas first + opts := &bind.CallOpts{Context: ctx} + postSgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, new(big.Int).Add(postSgtBalance, gasCost).Cmp(depositSgtValue), 0) + // balance invariant check + invariantBalanceCheck(t, ctx, sgt, testAddr, gasCost, txValue, depositSgtValue, depositL2Value, postSgtBalance) +} + +func fullSGTGasPaymentWithNativeBalanceSuccess(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(10000000000000) + depositL2Value := big.NewInt(10000000000000) + txValue := big.NewInt(0) + testAccount, testAddr, vaultBalanceBefore := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + tx, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, sgt.L2Client, tx.Hash()) + require.NoError(t, err) + gasCost := calcGasFee(receipt) + vaultBalanceAfter := calcVaultBalance(t, ctx, sgt) + + // gasCost == vaultBalanceDiff check + require.Equal(t, new(big.Int).Sub(vaultBalanceAfter, vaultBalanceBefore).Cmp(gasCost), 0) + // post sgt balance check: sgt should be used as gas first + opts := &bind.CallOpts{Context: ctx} + postSgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, new(big.Int).Add(postSgtBalance, gasCost).Cmp(depositSgtValue), 0) + // balance invariant check + invariantBalanceCheck(t, ctx, sgt, testAddr, gasCost, txValue, depositSgtValue, depositL2Value, postSgtBalance) +} + +func partialSGTGasPaymentSuccess(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + // 1000 is a random chosen value that is far less than the gas cos (~1225000231000) of the following `transferNativeToken` tx + depositSgtValue := big.NewInt(1000) + depositL2Value := big.NewInt(10000000000000) + txValue := big.NewInt(0) + testAccount, testAddr, vaultBalanceBefore := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + tx, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, sgt.L2Client, tx.Hash()) + require.NoError(t, err) + gasCost := calcGasFee(receipt) + vaultBalanceAfter := calcVaultBalance(t, ctx, sgt) + + // gasCost == vaultBalanceDiff check + require.Equal(t, new(big.Int).Sub(vaultBalanceAfter, vaultBalanceBefore).Cmp(gasCost), 0) + // post sgt balance check: sgt should be used as gas first and should be spent all + opts := &bind.CallOpts{Context: ctx} + postSgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, common.Big0.Cmp(postSgtBalance), 0) + // balance invariant check + invariantBalanceCheck(t, ctx, sgt, testAddr, gasCost, txValue, depositSgtValue, depositL2Value, postSgtBalance) +} + +func fullSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(10000000000000) + depositL2Value := big.NewInt(10000000000000) + txValue := big.NewInt(10000) + testAccount, testAddr, vaultBalanceBefore := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + tx, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, sgt.L2Client, tx.Hash()) + require.NoError(t, err) + gasCost := calcGasFee(receipt) + vaultBalanceAfter := calcVaultBalance(t, ctx, sgt) + + // gasCost == vaultBalanceDiff check + require.Equal(t, new(big.Int).Sub(vaultBalanceAfter, vaultBalanceBefore).Cmp(gasCost), 0) + // post sgt balance check: sgt should be used as gas first + opts := &bind.CallOpts{Context: ctx} + postSgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, new(big.Int).Add(postSgtBalance, gasCost).Cmp(depositSgtValue), 0) + // balance invariant check + invariantBalanceCheck(t, ctx, sgt, testAddr, gasCost, txValue, depositSgtValue, depositL2Value, postSgtBalance) +} + +func partialSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(1000) + depositL2Value := big.NewInt(10000000000000) + txValue := big.NewInt(10000) + testAccount, testAddr, vaultBalanceBefore := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + tx, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, sgt.L2Client, tx.Hash()) + require.NoError(t, err) + gasCost := calcGasFee(receipt) + vaultBalanceAfter := calcVaultBalance(t, ctx, sgt) + + // gasCost == vaultBalanceDiff check + require.Equal(t, new(big.Int).Sub(vaultBalanceAfter, vaultBalanceBefore).Cmp(gasCost), 0) + // post sgt balance check: sgt should be used as gas first and should be spent all + opts := &bind.CallOpts{Context: ctx} + postSgtBalance, err := sgt.SgtContract.BalanceOf(opts, testAddr) + require.NoError(t, err) + require.Equal(t, common.Big0.Cmp(postSgtBalance), 0) + // balance invariant check + invariantBalanceCheck(t, ctx, sgt, testAddr, gasCost, txValue, depositSgtValue, depositL2Value, postSgtBalance) +} + +func fullSGTInsufficientGasPaymentFail(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(10000) + depositL2Value := big.NewInt(0) + txValue := big.NewInt(0) + testAccount, _, _ := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + _, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.Error(t, err) +} + +func fullNativeInsufficientGasPaymentFail(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(0) + depositL2Value := big.NewInt(10000) + txValue := big.NewInt(0) + testAccount, _, _ := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + _, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.Error(t, err) +} + +func partialSGTInsufficientGasPaymentFail(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(10000) + depositL2Value := big.NewInt(10000) + txValue := big.NewInt(0) + testAccount, _, _ := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + _, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.Error(t, err) +} + +func fullSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(10000000000000) + depositL2Value := big.NewInt(10000) + txValue := big.NewInt(10001) + testAccount, _, _ := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + _, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.Error(t, err) +} + +func partialSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail(t *testing.T, ctx context.Context, index int64, sgt *SgtHelper) { + depositSgtValue := big.NewInt(10000) + depositL2Value := big.NewInt(10000000000000) + txValue := new(big.Int).Sub(depositL2Value, depositSgtValue) + testAccount, _, _ := setUpTestAccount(t, ctx, index, sgt, depositSgtValue, depositL2Value) + + // make a simple tx with the testAccount: transfer txValue from testAccount to dummyAddr + _, err := sgt.transferNativeToken(t, ctx, testAccount, dummyAddr, txValue) + require.Error(t, err) +} + +func calcGasFee(receipt *types.Receipt) *big.Int { + // OPStackTxFee = L2ExecutionGasFee + L1DataFee + fees := new(big.Int).Mul(receipt.EffectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) + fees = fees.Add(fees, receipt.L1Fee) + return fees +} + +func calcVaultBalance(t *testing.T, ctx context.Context, sgt *SgtHelper) *big.Int { + sequencerFee, err := sgt.L2Client.BalanceAt(ctx, seqVault, nil) + require.NoError(t, err) + baseFee, err := sgt.L2Client.BalanceAt(ctx, baseVault, nil) + require.NoError(t, err) + l1Fee, err := sgt.L2Client.BalanceAt(ctx, l1Vault, nil) + require.NoError(t, err) + return sequencerFee.Add(sequencerFee, baseFee.Add(baseFee, l1Fee)) +} diff --git a/op-e2e/system/altda/concurrent_test.go b/op-e2e/system/altda/concurrent_test.go index 4d6835b1fc551..e53c7f0f811be 100644 --- a/op-e2e/system/altda/concurrent_test.go +++ b/op-e2e/system/altda/concurrent_test.go @@ -34,7 +34,9 @@ func TestBatcherConcurrentAltDARequests(t *testing.T) { cfg.DisableBatcher = true sys, err := cfg.Start(t) require.NoError(t, err, "Error starting up system") - defer sys.Close() + t.Cleanup(func() { + sys.Close() + }) // make every request take 5 seconds, such that only concurrent requests will be able to make progress fast enough sys.FakeAltDAServer.SetPutRequestLatency(5 * time.Second) @@ -43,7 +45,7 @@ func TestBatcherConcurrentAltDARequests(t *testing.T) { l2Seq := sys.NodeClient("sequencer") // we wait for numL1TxsExpected L2 blocks to have been produced, just to make sure the sequencer is working properly - _, err = geth.WaitForBlock(big.NewInt(numL1TxsExpected), l2Seq, time.Duration(cfg.DeployConfig.L2BlockTime*uint64(numL1TxsExpected))*time.Second) + _, err = geth.WaitForBlock(big.NewInt(numL1TxsExpected), l2Seq) require.NoError(t, err, "Waiting for L2 blocks") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -55,23 +57,20 @@ func TestBatcherConcurrentAltDARequests(t *testing.T) { err = driver.StartBatchSubmitting() require.NoError(t, err) - totalBatcherTxsCount := int64(0) - // wait for up to 5 L1 blocks, expecting 10 L2 batcher txs in them. - // usually only 3 is required, but it's possible additional L1 blocks will be created - // before the batcher starts, so we wait additional blocks. - for i := int64(0); i < 5; i++ { - block, err := geth.WaitForBlock(big.NewInt(int64(startingL1BlockNum)+i), l1Client, time.Duration(cfg.DeployConfig.L1BlockTime*2)*time.Second) + // Iterate over up to 10 blocks. The number of transactions sent by the batcher should + // exceed the number of blocks. + checkBlocks := 10 + for i := 0; i < checkBlocks; i++ { + block, err := geth.WaitForBlock(big.NewInt(int64(startingL1BlockNum)+int64(i)), l1Client) require.NoError(t, err, "Waiting for l1 blocks") // there are possibly other services (proposer/challenger) in the background sending txs // so we only count the batcher txs batcherTxCount, err := transactions.TransactionsBySender(block, cfg.DeployConfig.BatchSenderAddress) require.NoError(t, err) - totalBatcherTxsCount += int64(batcherTxCount) - - if totalBatcherTxsCount >= numL1TxsExpected { + if batcherTxCount > 1 { return } } - t.Fatal("Expected at least 10 transactions from the batcher") + t.Fatalf("did not find more than 1 batcher tx per block in %d blocks", checkBlocks) } diff --git a/op-e2e/system/conductor/sequencer_failover_setup.go b/op-e2e/system/conductor/sequencer_failover_setup.go index ea515ecc1e64d..a4e5178e3d3e2 100644 --- a/op-e2e/system/conductor/sequencer_failover_setup.go +++ b/op-e2e/system/conductor/sequencer_failover_setup.go @@ -2,9 +2,8 @@ package conductor import ( "context" + "errors" "fmt" - "math/rand" - "net" "strings" "testing" "time" @@ -51,28 +50,23 @@ const ( var retryStrategy = &retry.FixedStrategy{Dur: 50 * time.Millisecond} type conductor struct { - service *con.OpConductor - client conrpc.API - consensusPort int - rpcPort int + service *con.OpConductor + client conrpc.API } func (c *conductor) ConsensusEndpoint() string { - return fmt.Sprintf("%s:%d", localhost, c.consensusPort) + return c.service.ConsensusEndpoint() } func (c *conductor) RPCEndpoint() string { - return fmt.Sprintf("http://%s:%d", localhost, c.rpcPort) + return c.service.HTTPEndpoint() } func setupSequencerFailoverTest(t *testing.T) (*e2esys.System, map[string]*conductor, func()) { op_e2e.InitParallel(t) ctx := context.Background() - sys, conductors, err := retry.Do2(ctx, maxSetupRetries, retryStrategy, func() (*e2esys.System, map[string]*conductor, error) { - return setupHAInfra(t, ctx) - }) - require.NoError(t, err, "Expected to successfully setup sequencers and conductors after retry") + sys, conductors := setupHAInfra(t, ctx) // form a cluster c1 := conductors[Sequencer1Name] @@ -143,79 +137,80 @@ func setupSequencerFailoverTest(t *testing.T) (*e2esys.System, map[string]*condu } } -func setupHAInfra(t *testing.T, ctx context.Context) (*e2esys.System, map[string]*conductor, error) { +func setupHAInfra(t *testing.T, ctx context.Context) (*e2esys.System, map[string]*conductor) { startTime := time.Now() - - var sys *e2esys.System - var conductors map[string]*conductor - var err error - - // clean up if setup fails due to port in use. defer func() { - if err != nil { - if sys != nil { - sys.Close() - } - - for _, c := range conductors { - if c == nil || c.service == nil { - // pass. Sometimes we can get nil in this map - } else if serr := c.service.Stop(ctx); serr != nil { - t.Log("Failed to stop conductor", "error", serr) - } - } - } t.Logf("setupHAInfra took %s\n", time.Since(startTime)) }() - conductorRpcPorts := map[string]int{ - Sequencer1Name: findAvailablePort(t), - Sequencer2Name: findAvailablePort(t), - Sequencer3Name: findAvailablePort(t), + conductorsReady := map[string]chan string{ + Sequencer1Name: make(chan string, 1), + Sequencer2Name: make(chan string, 1), + Sequencer3Name: make(chan string, 1), } - // 3 sequencers, 1 verifier, 1 active sequencer. - cfg := sequencerFailoverSystemConfig(t, conductorRpcPorts) - if sys, err = cfg.Start(t); err != nil { - return nil, nil, err + // The sequencer op-node & execution engine have to be up first, to get their endpoints running. + // The conductor is then started after, using the endpoints of op-node and execution engine. + // The op-node, while starting, will wait for the conductor to be up and running, to get its endpoint. + // No endpoint is reserved/hardcoded this way, this avoids CI test flakes in the setup. + conductorEndpointFn := func(ctx context.Context, name string) (endpoint string, err error) { + endpointCh, ok := conductorsReady[name] + if !ok { + return "", errors.New("conductor %s is not known") + } + select { + case <-ctx.Done(): + return "", fmt.Errorf("failed to set up conductor timely: %w", err) + case endpoint := <-endpointCh: + return endpoint, nil + } } - // 3 conductors that connects to 1 sequencer each. - conductors = make(map[string]*conductor) + // 3 sequencers, 1 verifier, 1 active sequencer. + cfg := sequencerFailoverSystemConfig(t, conductorEndpointFn) + + // sys is configured to close itself on test cleanup. + sys, err := cfg.Start(t) + require.NoError(t, err, "must start system") + + out := make(map[string]*conductor) + // 3 conductors that connects to 1 sequencer each. // initialize all conductors in paused mode conductorCfgs := []struct { name string - port int bootstrap bool }{ - {Sequencer1Name, conductorRpcPorts[Sequencer1Name], true}, // one in bootstrap mode so that we can form a cluster. - {Sequencer2Name, conductorRpcPorts[Sequencer2Name], false}, - {Sequencer3Name, conductorRpcPorts[Sequencer3Name], false}, + {Sequencer1Name, true}, // one in bootstrap mode so that we can form a cluster. + {Sequencer2Name, false}, + {Sequencer3Name, false}, } for _, cfg := range conductorCfgs { cfg := cfg nodePRC := sys.RollupNodes[cfg.name].UserRPC().RPC() engineRPC := sys.EthInstances[cfg.name].UserRPC().RPC() - if conductors[cfg.name], err = setupConductor(t, cfg.name, t.TempDir(), nodePRC, engineRPC, cfg.port, cfg.bootstrap, *sys.RollupConfig); err != nil { - return nil, nil, err - } + + conduc, err := setupConductor(t, cfg.name, t.TempDir(), nodePRC, engineRPC, cfg.bootstrap, *sys.RollupConfig) + require.NoError(t, err, "failed to set up conductor %s", cfg.name) + out[cfg.name] = conduc + // Signal that the conductor RPC endpoint is ready + conductorsReady[cfg.name] <- conduc.RPCEndpoint() } - return sys, conductors, nil + return sys, out } func setupConductor( t *testing.T, serverID, dir, nodeRPC, engineRPC string, - rpcPort int, bootstrap bool, rollupCfg rollup.Config, ) (*conductor, error) { - consensusPort := findAvailablePort(t) cfg := con.Config{ - ConsensusAddr: localhost, - ConsensusPort: consensusPort, + ConsensusAddr: localhost, + ConsensusPort: 0, // let the system select a port, avoid conflicts + ConsensusAdvertisedAddr: "", // use the local address we bind to + RaftServerID: serverID, RaftStorageDir: dir, RaftBootstrap: bootstrap, @@ -237,17 +232,18 @@ func setupConductor( RollupCfg: rollupCfg, RPCEnableProxy: true, LogConfig: oplog.CLIConfig{ - Level: log.LevelInfo, + Level: log.LevelDebug, Color: false, }, RPC: oprpc.CLIConfig{ ListenAddr: localhost, - ListenPort: rpcPort, + ListenPort: 0, // let the system select a port }, } + logger := testlog.Logger(t, log.LevelDebug) ctx := context.Background() - service, err := con.New(ctx, &cfg, testlog.Logger(t, log.LevelInfo), "0.0.1") + service, err := con.New(ctx, &cfg, logger, "0.0.1") if err != nil { return nil, err } @@ -257,6 +253,8 @@ func setupConductor( return nil, err } + logger.Info("Started conductor", "nodeRPC", nodeRPC, "engineRPC", engineRPC) + rawClient, err := rpc.DialContext(ctx, service.HTTPEndpoint()) if err != nil { return nil, err @@ -265,10 +263,8 @@ func setupConductor( client := conrpc.NewAPIClient(rawClient) return &conductor{ - service: service, - client: client, - consensusPort: consensusPort, - rpcPort: rpcPort, + service: service, + client: client, }, nil } @@ -316,12 +312,18 @@ func setupBatcher(t *testing.T, sys *e2esys.System, conductors map[string]*condu sys.BatchSubmitter = batcher } -func sequencerFailoverSystemConfig(t *testing.T, ports map[string]int) e2esys.SystemConfig { +func sequencerFailoverSystemConfig(t *testing.T, conductorRPCEndpoints func(ctx context.Context, name string) (string, error)) e2esys.SystemConfig { cfg := e2esys.EcotoneSystemConfig(t, new(hexutil.Uint64)) delete(cfg.Nodes, "sequencer") - cfg.Nodes[Sequencer1Name] = sequencerCfg(ports[Sequencer1Name]) - cfg.Nodes[Sequencer2Name] = sequencerCfg(ports[Sequencer2Name]) - cfg.Nodes[Sequencer3Name] = sequencerCfg(ports[Sequencer3Name]) + cfg.Nodes[Sequencer1Name] = sequencerCfg(func(ctx context.Context) (string, error) { + return conductorRPCEndpoints(ctx, Sequencer1Name) + }) + cfg.Nodes[Sequencer2Name] = sequencerCfg(func(ctx context.Context) (string, error) { + return conductorRPCEndpoints(ctx, Sequencer2Name) + }) + cfg.Nodes[Sequencer3Name] = sequencerCfg(func(ctx context.Context) (string, error) { + return conductorRPCEndpoints(ctx, Sequencer3Name) + }) delete(cfg.Loggers, "sequencer") cfg.Loggers[Sequencer1Name] = testlog.Logger(t, log.LevelInfo).New("role", Sequencer1Name) @@ -338,7 +340,7 @@ func sequencerFailoverSystemConfig(t *testing.T, ports map[string]int) e2esys.Sy return cfg } -func sequencerCfg(rpcPort int) *rollupNode.Config { +func sequencerCfg(conductorRPCEndpoint rollupNode.ConductorRPCFunc) *rollupNode.Config { return &rollupNode.Config{ Driver: driver.Config{ VerifierConfDepth: 0, @@ -357,8 +359,8 @@ func sequencerCfg(rpcPort int) *rollupNode.Config { ConfigPersistence: &rollupNode.DisabledConfigPersistence{}, Sync: sync.Config{SyncMode: sync.CLSync}, ConductorEnabled: true, - ConductorRpc: fmt.Sprintf("http://%s:%d", localhost, rpcPort), - ConductorRpcTimeout: 1 * time.Second, + ConductorRpc: conductorRPCEndpoint, + ConductorRpcTimeout: 5 * time.Second, } } @@ -453,26 +455,6 @@ func sequencerActive(t *testing.T, ctx context.Context, rollupClient *sources.Ro return active } -func findAvailablePort(t *testing.T) int { - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - for { - select { - case <-ctx.Done(): - t.Error("Failed to find available port") - default: - // private / ephemeral ports are in the range 49152-65535 - port := rand.Intn(65535-49152) + 49152 - addr := fmt.Sprintf("127.0.0.1:%d", port) - l, err := net.Listen("tcp", addr) - if err == nil { - l.Close() // Close the listener and return the port if it's available - return port - } - } - } -} - func findLeader(t *testing.T, conductors map[string]*conductor) (string, *conductor) { for id, con := range conductors { if leader(t, context.Background(), con) { diff --git a/op-e2e/system/conductor/sequencer_failover_test.go b/op-e2e/system/conductor/sequencer_failover_test.go index 1b49b989654f4..9004b528fc586 100644 --- a/op-e2e/system/conductor/sequencer_failover_test.go +++ b/op-e2e/system/conductor/sequencer_failover_test.go @@ -4,8 +4,10 @@ import ( "context" "sort" "testing" + "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/op-conductor/consensus" @@ -27,7 +29,8 @@ func TestSequencerFailover_SetupCluster(t *testing.T) { // [Category: conductor rpc] // In this test, we test all rpcs exposed by conductor. func TestSequencerFailover_ConductorRPC(t *testing.T) { - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() sys, conductors, cleanup := setupSequencerFailoverTest(t) defer cleanup() @@ -102,7 +105,6 @@ func TestSequencerFailover_ConductorRPC(t *testing.T) { t, VerifierName, t.TempDir(), sys.RollupEndpoint(Sequencer3Name).RPC(), sys.NodeEndpoint(Sequencer3Name).RPC(), - findAvailablePort(t), false, *sys.RollupConfig, ) @@ -176,7 +178,8 @@ func TestSequencerFailover_ActiveSequencerDown(t *testing.T) { sys, conductors, cleanup := setupSequencerFailoverTest(t) defer cleanup() - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() leaderId, leader := findLeader(t, conductors) err := sys.RollupNodes[leaderId].Stop(ctx) // Stop the current leader sequencer require.NoError(t, err) @@ -205,7 +208,8 @@ func TestSequencerFailover_DisasterRecovery_OverrideLeader(t *testing.T) { defer cleanup() // randomly stop 2 nodes in the cluster to simulate a disaster. - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() err := conductors[Sequencer1Name].service.Stop(ctx) require.NoError(t, err) err = conductors[Sequencer2Name].service.Stop(ctx) @@ -232,9 +236,45 @@ func TestSequencerFailover_DisasterRecovery_OverrideLeader(t *testing.T) { require.NoError(t, err) require.True(t, active, "Expected sequencer to be active") - err = conductors[Sequencer3Name].client.OverrideLeader(ctx) + err = conductors[Sequencer3Name].client.OverrideLeader(ctx, true) require.NoError(t, err) leader, err := conductors[Sequencer3Name].client.Leader(ctx) require.NoError(t, err) require.True(t, leader, "Expected conductor to return leader true after override") + overridden, err := conductors[Sequencer3Name].client.LeaderOverridden(ctx) + require.NoError(t, err) + require.True(t, overridden, "Expected conductor to return leader overridden true after override") + + // make sure all proxied method are working correctly. + proxy, err := rpc.DialContext(ctx, conductors[Sequencer3Name].RPCEndpoint()) + require.NoError(t, err) + err = proxy.CallContext(ctx, &active, "admin_sequencerActive") + require.NoError(t, err) + require.True(t, active, "Expected sequencer to be active") + err = proxy.CallContext(ctx, nil, "optimism_syncStatus") + require.NoError(t, err) + var block map[string]any + err = proxy.CallContext(ctx, &block, "eth_getBlockByNumber", "latest", false) + require.NoError(t, err) + err = proxy.CallContext(ctx, nil, "optimism_outputAtBlock", block["number"]) + require.NoError(t, err) + err = proxy.CallContext(ctx, nil, "optimism_rollupConfig") + require.NoError(t, err) + + err = conductors[Sequencer3Name].client.OverrideLeader(ctx, false) + require.NoError(t, err) + overridden, err = conductors[Sequencer3Name].client.LeaderOverridden(ctx) + require.NoError(t, err) + require.False(t, overridden, "Expected conductor to return leader overridden false after override") + + err = proxy.CallContext(ctx, &active, "admin_sequencerActive") + require.ErrorContains(t, err, "refusing to proxy request to non-leader sequencer", "Expected sequencer to fail to get active status") + err = proxy.CallContext(ctx, nil, "optimism_syncStatus") + require.ErrorContains(t, err, "refusing to proxy request to non-leader sequencer", "Expected sequencer to fail to get sync status") + err = proxy.CallContext(ctx, nil, "eth_getBlockByNumber", "latest", false) + require.ErrorContains(t, err, "refusing to proxy request to non-leader sequencer", "Expected sequencer to fail to get block by number") + err = proxy.CallContext(ctx, nil, "optimism_outputAtBlock", block["number"]) + require.ErrorContains(t, err, "refusing to proxy request to non-leader sequencer", "Expected sequencer to fail to get output at block") + err = proxy.CallContext(ctx, nil, "optimism_rollupConfig") + require.ErrorContains(t, err, "refusing to proxy request to non-leader sequencer", "Expected sequencer to fail to get rollup config") } diff --git a/op-e2e/system/da/brotli_batcher_test.go b/op-e2e/system/da/brotli_batcher_test.go index a55e5bced8ade..fd44c6365eae8 100644 --- a/op-e2e/system/da/brotli_batcher_test.go +++ b/op-e2e/system/da/brotli_batcher_test.go @@ -8,6 +8,7 @@ import ( "time" op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" "github.com/ethereum-optimism/optimism/op-e2e/system/helpers" @@ -42,7 +43,7 @@ func setupAliceAccount(t *testing.T, cfg e2esys.SystemConfig, sys *e2esys.System require.NoError(t, err) mintAmount := big.NewInt(1_000_000_000_000) opts.Value = mintAmount - helpers.SendDepositTx(t, cfg, l1Client, l2Verif, opts, func(l2Opts *helpers.DepositTxOpts) {}) + helpers.SendDepositTx(t, cfg, l1Client, l2Verif, opts, nil) // Confirm balance ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second) @@ -67,7 +68,7 @@ func TestBrotliBatcherFjord(t *testing.T) { cfg.DeployConfig.L2GenesisFjordTimeOffset = &genesisActivation // set up batcher to use brotli - sys, err := cfg.Start(t, e2esys.StartOption{Key: "compressionAlgo", Role: "brotli", Action: nil}) + sys, err := cfg.Start(t, e2esys.WithBatcherCompressionAlgo(derive.Brotli)) require.Nil(t, err, "Error starting up system") log := testlog.Logger(t, log.LevelInfo) diff --git a/op-e2e/system/da/da_throttling_test.go b/op-e2e/system/da/da_throttling_test.go new file mode 100644 index 0000000000000..e98ad9d3338d2 --- /dev/null +++ b/op-e2e/system/da/da_throttling_test.go @@ -0,0 +1,192 @@ +package da + +import ( + "context" + "crypto/ecdsa" + "crypto/rand" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/node" + + "github.com/ethereum-optimism/optimism/op-batcher/batcher" + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + "github.com/ethereum-optimism/optimism/op-service/sources" +) + +const ( + bigTxSize = 10000 // amount of incompressible calldata to put in a "big" transaction +) + +func TestDATxThrottling(t *testing.T) { + op_e2e.InitParallel(t) + + cfg, rollupClient, l2Seq, l2Verif, batcher := setupTest(t, 100, 0) + + sendTx := func(senderKey *ecdsa.PrivateKey, nonce uint64, size int) *types.Receipt { + hash := sendTx(t, senderKey, nonce, size, cfg.L2ChainIDBig(), l2Seq) + return waitForReceipt(t, hash, l2Seq) + } + + // send a big transaction before throttling could have started, this transaction should land + receipt := sendTx(cfg.Secrets.Alice, 0, bigTxSize) + + // start batch submission, which should trigger throttling future large transactions + err := batcher.StartBatchSubmitting() + require.NoError(t, err) + + // wait until the block containing the above tx shows up as safe to confirm batcher is running. + waitForBlock(t, receipt.BlockNumber, l2Verif, rollupClient) + + // send another big tx, this one should get "stuck" so we wait for its receipt in a parallel goroutine. + done := make(chan bool, 1) + var bigReceipt *types.Receipt + go func() { + bigReceipt = sendTx(cfg.Secrets.Alice, 1, bigTxSize) + done <- true + }() + + safeBlockInclusionDuration := time.Duration(6*cfg.DeployConfig.L1BlockTime) * time.Second + time.Sleep(safeBlockInclusionDuration) + require.Nil(t, bigReceipt, "large tx did not get throttled") + + // Send a small tx, it should get included before the earlier one as long as it's from another sender + r := sendTx(cfg.Secrets.Bob, 0, 0) + // wait until the block the tx was first included in shows up in the safe chain on the verifier + waitForBlock(t, r.BlockNumber, l2Verif, rollupClient) + + // second tx should still be throttled + require.Nil(t, bigReceipt, "large tx did not get throttled") + + // disable throttling to let big tx through + batcher.Config.ThrottleTxSize = 0 + <-done + require.NotNil(t, bigReceipt, "large tx did not get throttled") +} + +func TestDABlockThrottling(t *testing.T) { + op_e2e.InitParallel(t) + cfg, rollupClient, l2Seq, l2Verif, batcher := setupTest(t, 0, bigTxSize+bigTxSize/10) + + sendTx := func(senderKey *ecdsa.PrivateKey, nonce uint64, size int) common.Hash { + return sendTx(t, senderKey, nonce, size, cfg.L2ChainIDBig(), l2Seq) + } + + // Send three big transactions before throttling could have started and make sure some eventually appear in the same + // block to confirm there is no block-level DA throttling active. This usually happens the first try but might + // require a second iteration in some cases due to stochasticity. + nonce := uint64(0) + for { + h1 := sendTx(cfg.Secrets.Alice, nonce, bigTxSize) + h2 := sendTx(cfg.Secrets.Bob, nonce, bigTxSize) + h3 := sendTx(cfg.Secrets.Mallory, nonce, bigTxSize) + nonce++ + + r1 := waitForReceipt(t, h1, l2Seq) + r2 := waitForReceipt(t, h2, l2Seq) + r3 := waitForReceipt(t, h3, l2Seq) + + // wait until the blocks containing the above txs show up in the unsafe chain + waitForBlock(t, r1.BlockNumber, l2Seq, rollupClient) + waitForBlock(t, r2.BlockNumber, l2Seq, rollupClient) + waitForBlock(t, r3.BlockNumber, l2Seq, rollupClient) + t.Log("Some block numbers should be the same:", r1.BlockNumber, r2.BlockNumber, r3.BlockNumber) + + if r1.BlockNumber.Cmp(r2.BlockNumber) == 0 || r1.BlockNumber.Cmp(r3.BlockNumber) == 0 || r2.BlockNumber.Cmp(r3.BlockNumber) == 0 { + // At least 2 transactions appeared in the same block, so we can exit the loop. + // But first we start batch submission, which will enabling DA throttling. + err := batcher.StartBatchSubmitting() + require.NoError(t, err) + // wait for a safe block containing one of the above transactions to ensure the batcher is running + waitForBlock(t, r1.BlockNumber, l2Verif, rollupClient) + break + } + t.Log("Another iteration required:", nonce) + } + + // Send 3 more big transactions at a time, but this time they must all appear in different blocks due to the + // block-level DA limit. Repeat the test 3 times to reduce the probability this happened just due to bad luck. + for i := 0; i < 3; i++ { + h1 := sendTx(cfg.Secrets.Alice, nonce, bigTxSize) + h2 := sendTx(cfg.Secrets.Bob, nonce, bigTxSize) + h3 := sendTx(cfg.Secrets.Mallory, nonce, bigTxSize) + nonce++ + + r1 := waitForReceipt(t, h1, l2Seq) + r2 := waitForReceipt(t, h2, l2Seq) + r3 := waitForReceipt(t, h3, l2Seq) + t.Log("Block numbers should all be different:", r1.BlockNumber, r2.BlockNumber, r3.BlockNumber) + + require.NotEqual(t, 0, r1.BlockNumber.Cmp(r2.BlockNumber)) + require.NotEqual(t, 0, r1.BlockNumber.Cmp(r3.BlockNumber)) + require.NotEqual(t, 0, r2.BlockNumber.Cmp(r3.BlockNumber)) + } +} + +func setupTest(t *testing.T, maxTxSize, maxBlockSize uint64) (e2esys.SystemConfig, *sources.RollupClient, *ethclient.Client, *ethclient.Client, *batcher.TestBatchSubmitter) { + cfg := e2esys.DefaultSystemConfig(t) + cfg.GethOptions["sequencer"] = append(cfg.GethOptions["sequencer"], []geth.GethOption{ + func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error { + ethCfg.Miner.GasCeil = 30_000_000 + return nil + }, + }...) + // disable batcher because we start it manually later + cfg.DisableBatcher = true + + sys, err := cfg.Start(t, + e2esys.WithBatcherThrottling(500*time.Millisecond, 1, maxTxSize, maxBlockSize)) + require.NoError(t, err, "Error starting up system") + + rollupClient := sys.RollupClient("verifier") + l2Seq := sys.NodeClient("sequencer") + l2Verif := sys.NodeClient("verifier") + batcher := sys.BatchSubmitter.ThrottlingTestDriver() + + return cfg, rollupClient, l2Seq, l2Verif, batcher +} + +// sendTx sends a tx containing the 'size' amount of random calldata +func sendTx(t *testing.T, senderKey *ecdsa.PrivateKey, nonce uint64, size int, chainID *big.Int, cl *ethclient.Client) common.Hash { + randomBytes := make([]byte, size) + _, err := rand.Read(randomBytes) + if err != nil { + panic(err) + } + tx := types.MustSignNewTx(senderKey, types.LatestSignerForChainID(chainID), &types.DynamicFeeTx{ + ChainID: chainID, + Nonce: nonce, + To: &common.Address{0xff, 0xff}, + Value: big.NewInt(1_000_000_000), + GasTipCap: big.NewInt(10), + GasFeeCap: big.NewInt(200), + Gas: 21_000 + uint64(len(randomBytes))*16, + Data: randomBytes, + }) + err = cl.SendTransaction(context.Background(), tx) + require.NoError(t, err, "sending L2 tx") + return tx.Hash() +} + +func waitForReceipt(t *testing.T, hash common.Hash, cl *ethclient.Client) *types.Receipt { + receipt, err := wait.ForReceiptOK(context.Background(), cl, hash) + require.NoError(t, err, "waiting for L2 tx") + require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "tx not successful") + return receipt +} + +func waitForBlock(t *testing.T, blockNumber *big.Int, cl *ethclient.Client, rc *sources.RollupClient) { + _, err := geth.WaitForBlock(blockNumber, cl) + require.NoError(t, err, "Waiting for block on verifier") + require.NoError(t, wait.ForProcessingFullBatch(context.Background(), rc)) +} diff --git a/op-e2e/system/da/eip4844_test.go b/op-e2e/system/da/eip4844_test.go index f3cf8fc7f03fa..e1b6468378e96 100644 --- a/op-e2e/system/da/eip4844_test.go +++ b/op-e2e/system/da/eip4844_test.go @@ -57,7 +57,6 @@ func testSystem4844E2E(t *testing.T, multiBlob bool, daType batcherFlags.DataAva cfg.BatcherBatchType = derive.SpanBatchType cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7000)) - const maxBlobs = eth.MaxBlobsPerBlobTx var maxL1TxSize int if multiBlob { cfg.BatcherTargetNumFrames = eth.MaxBlobsPerBlobTx @@ -120,7 +119,7 @@ func testSystem4844E2E(t *testing.T, multiBlob bool, daType batcherFlags.DataAva require.NoError(t, err) mintAmount := big.NewInt(1_000_000_000_000) opts.Value = mintAmount - helpers.SendDepositTx(t, cfg, l1Client, l2Verif, opts, func(l2Opts *helpers.DepositTxOpts) {}) + helpers.SendDepositTx(t, cfg, l1Client, l2Verif, opts, nil) // Confirm balance ctx2, cancel2 := context.WithTimeout(context.Background(), 20*time.Second) @@ -214,7 +213,8 @@ func testSystem4844E2E(t *testing.T, multiBlob bool, daType batcherFlags.DataAva if !multiBlob { require.NotZero(t, numBlobs, "single-blob: expected to find L1 blob tx") } else { - require.Equal(t, maxBlobs, numBlobs, fmt.Sprintf("multi-blob: expected to find L1 blob tx with %d blobs", eth.MaxBlobsPerBlobTx)) + const maxBlobs = eth.MaxBlobsPerBlobTx + require.Equal(t, maxBlobs, numBlobs, fmt.Sprintf("multi-blob: expected to find L1 blob tx with %d blobs", maxBlobs)) // blob tx should have filled up all but last blob bcl := sys.L1BeaconHTTPClient() hashes := toIndexedBlobHashes(blobTx.BlobHashes()...) diff --git a/op-e2e/system/da/multi_test.go b/op-e2e/system/da/multi_test.go index 8272930da765b..461270282008b 100644 --- a/op-e2e/system/da/multi_test.go +++ b/op-e2e/system/da/multi_test.go @@ -29,13 +29,11 @@ func TestBatcherMultiTx(t *testing.T) { l1Client := sys.NodeClient("l1") l2Seq := sys.NodeClient("sequencer") - _, err = geth.WaitForBlock(big.NewInt(10), l2Seq, time.Duration(cfg.DeployConfig.L2BlockTime*15)*time.Second) + _, err = geth.WaitForBlock(big.NewInt(10), l2Seq) require.NoError(t, err, "Waiting for L2 blocks") - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - l1Number, err := l1Client.BlockNumber(ctx) - require.NoError(t, err) // start batch submission driver := sys.BatchSubmitter.TestDriver() @@ -43,21 +41,33 @@ func TestBatcherMultiTx(t *testing.T) { require.NoError(t, err) totalBatcherTxsCount := int64(0) - // wait for up to 5 L1 blocks, usually only 3 is required, but it's - // possible additional L1 blocks will be created before the batcher starts, - // so we wait additional blocks. - for i := int64(0); i < 5; i++ { - block, err := geth.WaitForBlock(big.NewInt(int64(l1Number)+i), l1Client, time.Duration(cfg.DeployConfig.L1BlockTime*2)*time.Second) - require.NoError(t, err, "Waiting for l1 blocks") - // there are possibly other services (proposer/challenger) in the background sending txs - // so we only count the batcher txs - batcherTxCount, err := transactions.TransactionsBySender(block, cfg.DeployConfig.BatchSenderAddress) - require.NoError(t, err) - totalBatcherTxsCount += int64(batcherTxCount) - if totalBatcherTxsCount >= 10 { - return + headNum, err := l1Client.BlockNumber(ctx) + require.NoError(t, err) + stopNum := headNum + 10 + startBlock := uint64(1) + + for { + for i := startBlock; i <= headNum; i++ { + block, err := l1Client.BlockByNumber(ctx, big.NewInt(int64(i))) + require.NoError(t, err) + + batcherTxCount, err := transactions.TransactionsBySender(block, cfg.DeployConfig.BatchSenderAddress) + require.NoError(t, err) + totalBatcherTxsCount += batcherTxCount + + if totalBatcherTxsCount >= 10 { + return + } + } + + headNum++ + if headNum > stopNum { + break } + startBlock = headNum + _, err = geth.WaitForBlock(big.NewInt(int64(headNum)), l1Client) + require.NoError(t, err) } t.Fatal("Expected at least 10 transactions from the batcher") diff --git a/op-e2e/system/da/startstop_test.go b/op-e2e/system/da/startstop_test.go index e085029983af1..2a5e0826f06ef 100644 --- a/op-e2e/system/da/startstop_test.go +++ b/op-e2e/system/da/startstop_test.go @@ -72,7 +72,7 @@ func testStartStopBatcher(t *testing.T, cfgMod func(*e2esys.SystemConfig)) { // wait until the block the tx was first included in shows up in the safe chain on the verifier safeBlockInclusionDuration := time.Duration(6*cfg.DeployConfig.L1BlockTime) * time.Second - _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration) + _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif) require.NoError(t, err, "Waiting for block on verifier") require.NoError(t, wait.ForProcessingFullBatch(context.Background(), rollupClient)) @@ -111,7 +111,7 @@ func testStartStopBatcher(t *testing.T, cfgMod func(*e2esys.SystemConfig)) { receipt = sendTx() // wait until the block the tx was first included in shows up in the safe chain on the verifier - _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif, safeBlockInclusionDuration) + _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif) require.NoError(t, err, "Waiting for block on verifier") require.NoError(t, wait.ForProcessingFullBatch(context.Background(), rollupClient)) diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index ad1969eeed8f9..e59401d905760 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -16,6 +16,8 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -83,6 +85,13 @@ var ( genesisTime = hexutil.Uint64(0) ) +func DefaultSystemConfigForSoulGasToken(t *testing.T, enable, isBackedByNative bool) SystemConfig { + config := DefaultSystemConfig(t) + config.UseSoulGasToken = enable + config.IsSoulBackedByNative = isBackedByNative + return config +} + type SystemConfigOpts struct { AllocType config.AllocType } @@ -193,6 +202,7 @@ func RegolithSystemConfig(t *testing.T, regolithTimeOffset *hexutil.Uint64, opts cfg.DeployConfig.L2GenesisEcotoneTimeOffset = nil cfg.DeployConfig.L2GenesisFjordTimeOffset = nil cfg.DeployConfig.L2GenesisGraniteTimeOffset = nil + cfg.DeployConfig.L2GenesisHoloceneTimeOffset = nil // ADD NEW FORKS HERE! return cfg } @@ -229,6 +239,12 @@ func GraniteSystemConfig(t *testing.T, graniteTimeOffset *hexutil.Uint64, opts . return cfg } +func HoloceneSystemConfig(t *testing.T, holoceneTimeOffset *hexutil.Uint64, opts ...SystemConfigOpt) SystemConfig { + cfg := GraniteSystemConfig(t, &genesisTime, opts...) + cfg.DeployConfig.L2GenesisHoloceneTimeOffset = holoceneTimeOffset + return cfg +} + func writeDefaultJWT(t testing.TB) string { // Sadly the geth node config cannot load JWT secret from memory, it has to be a file jwtPath := path.Join(t.TempDir(), "jwt_secret") @@ -318,6 +334,11 @@ type SystemConfig struct { // SupportL1TimeTravel determines if the L1 node supports quickly skipping forward in time SupportL1TimeTravel bool + // Use Soul Gas Token to pay tx fee + UseSoulGasToken bool + // Whether the Soul Gas Token is backed by native gas token or not + IsSoulBackedByNative bool + AllocType config.AllocType } @@ -464,6 +485,9 @@ type StartOption struct { Key string Role string Action SystemConfigHook + + // Batcher CLIConfig modifications to apply before starting the batcher. + BatcherMod func(*bss.CLIConfig) } type startOptions struct { @@ -484,6 +508,25 @@ func parseStartOptions(_opts []StartOption) (startOptions, error) { }, nil } +func WithBatcherCompressionAlgo(ca derive.CompressionAlgo) StartOption { + return StartOption{ + BatcherMod: func(cfg *bss.CLIConfig) { + cfg.CompressionAlgo = ca + }, + } +} + +func WithBatcherThrottling(interval time.Duration, threshold, txSize, blockSize uint64) StartOption { + return StartOption{ + BatcherMod: func(cfg *bss.CLIConfig) { + cfg.ThrottleInterval = interval + cfg.ThrottleThreshold = threshold + cfg.ThrottleTxSize = txSize + cfg.ThrottleBlockSize = blockSize + }, + } +} + func (s *startOptions) Get(key, role string) (SystemConfigHook, bool) { v, ok := s.opts[key+":"+role] return v, ok @@ -606,6 +649,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, EcotoneTime: cfg.DeployConfig.EcotoneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, AltDAConfig: rollupAltDAConfig, @@ -641,6 +685,14 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, return nil, err } + sysLogger := testlog.Logger(t, log.LevelInfo).New("role", "system") + + l1UpCtx, l1UpCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer l1UpCancel() + if err := wait.ForNodeUp(l1UpCtx, sys.NodeClient(RoleL1), sysLogger); err != nil { + return nil, fmt.Errorf("l1 never came up: %w", err) + } + // Ordered such that the Sequencer is initialized first. Setup this way so that // the `RollupSequencerHTTP` GethOption can be supplied to any sentry nodes. l2Nodes := []string{RoleSeq} @@ -682,7 +734,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, } l1Client := sys.NodeClient(RoleL1) - _, err = geth.WaitForBlock(big.NewInt(2), l1Client, 6*time.Second*time.Duration(cfg.DeployConfig.L1BlockTime)) + _, err = geth.WaitForBlock(big.NewInt(2), l1Client) if err != nil { return nil, fmt.Errorf("waiting for blocks: %w", err) } @@ -851,12 +903,6 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, batcherTargetNumFrames = 1 } - var compressionAlgo derive.CompressionAlgo = derive.Zlib - // if opt has brotli key, set the compression algo as brotli - if _, ok := parsedStartOpts.Get("compressionAlgo", "brotli"); ok { - compressionAlgo = derive.Brotli10 - } - var batcherAltDACLIConfig altda.CLIConfig if cfg.DeployConfig.UseAltDA { fakeAltDAServer := altda.NewFakeDAServer("127.0.0.1", 0, sys.Cfg.Loggers["da-server"]) @@ -894,9 +940,17 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, BatchType: cfg.BatcherBatchType, MaxBlocksPerSpanBatch: cfg.BatcherMaxBlocksPerSpanBatch, DataAvailabilityType: sys.Cfg.DataAvailabilityType, - CompressionAlgo: compressionAlgo, + CompressionAlgo: derive.Zlib, AltDA: batcherAltDACLIConfig, } + + // Apply batcher cli modifications + for _, opt := range startOpts { + if opt.BatcherMod != nil { + opt.BatcherMod(batcherCLIConfig) + } + } + // Batch Submitter batcher, err := bss.BatcherServiceFromCLIConfig(context.Background(), "0.0.1", batcherCLIConfig, sys.Cfg.Loggers["batcher"]) if err != nil { @@ -1015,7 +1069,10 @@ func (sys *System) RollupClient(name string) *sources.RollupClient { require.NoError(sys.t, err, "failed to dial rollup instance %s", name) return cl }) - rollupClient = sources.NewRollupClient(client.NewBaseRPCClient(rpcClient)) + rollupClient = sources.NewRollupClient(client.NewBaseRPCClient(rpcClient, + // Increase timeouts because CI servers can be under a lot of load + client.WithCallTimeout(30*time.Second), + client.WithBatchCallTimeout(30*time.Second))) sys.rollupClients[name] = rollupClient return rollupClient } diff --git a/op-e2e/system/fees/eip1559params_test.go b/op-e2e/system/fees/eip1559params_test.go new file mode 100644 index 0000000000000..e1b1098cac129 --- /dev/null +++ b/op-e2e/system/fees/eip1559params_test.go @@ -0,0 +1,104 @@ +package fees + +import ( + "context" + "math/big" + "testing" + "time" + + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + + legacybindings "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/stretchr/testify/require" +) + +// TestEIP1599Params checks that we can successfully change EIP-1559 parameters via SysConfig with +// the Holocene upgrade. +func TestEIP1559Params(t *testing.T) { + op_e2e.InitParallel(t) + + ctx, ctxCancel := context.WithCancel(context.Background()) + defer ctxCancel() + + // Create our system configuration for L1/L2 and start it + cfg := e2esys.HoloceneSystemConfig(t, new(hexutil.Uint64)) + cfg.DeployConfig.L2GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(100_000_000)) + sys, err := cfg.Start(t) + require.NoError(t, err, "Error starting up system") + + // Obtain our sequencer, verifier, and transactor keypair. + l1Client := sys.NodeClient("l1") + l2Seq := sys.NodeClient("sequencer") + ethPrivKey := cfg.Secrets.SysCfgOwner + + _, err = l2Seq.HeaderByNumber(context.Background(), big.NewInt(0)) + require.NoError(t, err) + + // Bind to the SystemConfig contract + sysconfig, err := legacybindings.NewSystemConfig(cfg.L1Deployments.SystemConfigProxy, l1Client) + require.NoError(t, err) + + // Obtain our signer. + opts, err := bind.NewKeyedTransactorWithChainID(ethPrivKey, cfg.L1ChainIDBig()) + require.NoError(t, err) + + // Define our L1 transaction timeout duration. + txTimeoutDuration := 10 * time.Duration(cfg.DeployConfig.L1BlockTime) * time.Second + + var cancel context.CancelFunc + + // confirm eip-1559 parameters are initialized to 0 + denom, err := sysconfig.Eip1559Denominator(&bind.CallOpts{}) + require.NoError(t, err, "reading denominator") + require.Equal(t, uint32(0), denom) + + elasticity, err := sysconfig.Eip1559Elasticity(&bind.CallOpts{}) + require.NoError(t, err, "reading elasticity") + require.Equal(t, uint32(0), elasticity) + + // update the EIP-1559 params, wait for it to show up on L2, & verify that it was set as intended + expectedDenom := uint32(10) + expectedElasticity := uint32(2) // implies gas target will be 15M since block limit is 30M + const gasTarget = 15_000_000 + opts.Context, cancel = context.WithTimeout(ctx, txTimeoutDuration) + tx, err := sysconfig.SetEIP1559Params(opts, expectedDenom, expectedElasticity) + cancel() + require.NoError(t, err, "SetEIP1559Params update tx") + + receipt, err := wait.ForReceiptOK(ctx, l1Client, tx.Hash()) + require.NoError(t, err, "Waiting for sysconfig set gas config update tx") + + denom, err = sysconfig.Eip1559Denominator(&bind.CallOpts{}) + require.NoError(t, err, "reading denominator") + require.Equal(t, expectedDenom, denom) + + elasticity, err = sysconfig.Eip1559Elasticity(&bind.CallOpts{}) + require.NoError(t, err, "reading elasticity") + require.Equal(t, expectedElasticity, elasticity) + + _, err = geth.WaitForL1OriginOnL2(sys.RollupConfig, receipt.BlockNumber.Uint64(), l2Seq, txTimeoutDuration) + require.NoError(t, err, "waiting for L2 block to include the sysconfig update") + + h, err := l2Seq.HeaderByNumber(context.Background(), nil) + require.NoError(t, err) + + // confirm the extraData is being set as expected + require.Equal(t, eip1559.EncodeHoloceneExtraData(uint64(expectedDenom), uint64(expectedElasticity)), h.Extra) + + // confirm the next base fee will be as expected with the new 1559 parameters + delta := ((gasTarget - int64(h.GasUsed)) * h.BaseFee.Int64() / gasTarget / int64(expectedDenom)) + expectedNextFee := h.BaseFee.Int64() - delta + + b, err := geth.WaitForBlock(big.NewInt(h.Number.Int64()+1), l2Seq) + require.NoError(t, err, "waiting for next L2 block") + require.Equal(t, expectedNextFee, b.Header().BaseFee.Int64()) + + // confirm the extraData is still being set as expected + require.Equal(t, eip1559.EncodeHoloceneExtraData(uint64(expectedDenom), uint64(expectedElasticity)), b.Header().Extra) +} diff --git a/op-e2e/system/fees/fees_test.go b/op-e2e/system/fees/fees_test.go index 61590313cbc4f..6296b0ee7ab4f 100644 --- a/op-e2e/system/fees/fees_test.go +++ b/op-e2e/system/fees/fees_test.go @@ -4,7 +4,6 @@ import ( "context" "math/big" "testing" - "time" op_e2e "github.com/ethereum-optimism/optimism/op-e2e" @@ -90,7 +89,7 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { l1 := sys.NodeClient("l1") // Wait for first block after genesis. The genesis block has zero L1Block values and will throw off the GPO checks - _, err = geth.WaitForBlock(big.NewInt(1), l2Verif, time.Minute) + _, err = geth.WaitForBlock(big.NewInt(1), l2Verif) require.NoError(t, err) config := sys.L2Genesis().Config diff --git a/op-e2e/system/fees/l1info_test.go b/op-e2e/system/fees/l1info_test.go index 2fdd3f70747a0..a4fc16d94a595 100644 --- a/op-e2e/system/fees/l1info_test.go +++ b/op-e2e/system/fees/l1info_test.go @@ -113,9 +113,9 @@ func TestL1InfoContract(t *testing.T) { endVerifBlockNumber := big.NewInt(4) endSeqBlockNumber := big.NewInt(6) - endVerifBlock, err := geth.WaitForBlock(endVerifBlockNumber, l2Verif, time.Minute) + endVerifBlock, err := geth.WaitForBlock(endVerifBlockNumber, l2Verif) require.Nil(t, err) - endSeqBlock, err := geth.WaitForBlock(endSeqBlockNumber, l2Seq, time.Minute) + endSeqBlock, err := geth.WaitForBlock(endSeqBlockNumber, l2Seq) require.Nil(t, err) seqL1Info, err := bindings.NewL1Block(cfg.L1InfoPredeployAddress, l2Seq) diff --git a/op-e2e/system/gastoken/gastoken_test.go b/op-e2e/system/gastoken/gastoken_test.go index 4b2e6009c3f2e..b5f3f56e2f6c9 100644 --- a/op-e2e/system/gastoken/gastoken_test.go +++ b/op-e2e/system/gastoken/gastoken_test.go @@ -27,6 +27,14 @@ import ( "github.com/stretchr/testify/require" ) +// setup expectations using custom gas token +type cgtTestExpectations struct { + tokenAddress common.Address + tokenName string + tokenSymbol string + tokenDecimals uint8 +} + func TestMain(m *testing.M) { op_e2e.RunMain(m) } @@ -41,413 +49,104 @@ func TestCustomGasToken_Standard(t *testing.T) { func testCustomGasToken(t *testing.T, allocType config.AllocType) { op_e2e.InitParallel(t) - cfg := e2esys.DefaultSystemConfig(t, e2esys.WithAllocType(allocType)) - offset := hexutil.Uint64(0) - cfg.DeployConfig.L2GenesisRegolithTimeOffset = &offset - cfg.DeployConfig.L1CancunTimeOffset = &offset - cfg.DeployConfig.L2GenesisCanyonTimeOffset = &offset - cfg.DeployConfig.L2GenesisDeltaTimeOffset = &offset - cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &offset - - sys, err := cfg.Start(t) - require.NoError(t, err, "Error starting up system") - l1Client := sys.NodeClient("l1") - l2Client := sys.NodeClient("sequencer") - - aliceOpts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L1ChainIDBig()) - require.NoError(t, err) - - // Deploy WETH9, we'll use this as our custom gas token for the purpose of the test - weth9Address, tx, weth9, err := bindings.DeployWETH9(aliceOpts, l1Client) - require.NoError(t, err) - _, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) - require.NoError(t, err) - - // setup expectations using custom gas token - type Expectations struct { - tokenAddress common.Address - tokenName string - tokenSymbol string - tokenDecimals uint8 - } - disabledExpectations := Expectations{ + disabledExpectations := cgtTestExpectations{ common.HexToAddress("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), "Ether", "ETH", uint8(18), } - enabledExpectations := Expectations{} - enabledExpectations.tokenAddress = weth9Address - enabledExpectations.tokenName, err = weth9.Name(&bind.CallOpts{}) - require.NoError(t, err) - enabledExpectations.tokenSymbol, err = weth9.Symbol(&bind.CallOpts{}) - require.NoError(t, err) - enabledExpectations.tokenDecimals, err = weth9.Decimals(&bind.CallOpts{}) - require.NoError(t, err) - - // Get some WETH - aliceOpts.Value = big.NewInt(10_000_000) - tx, err = weth9.Deposit(aliceOpts) - waitForTx(t, tx, err, l1Client) - aliceOpts.Value = nil - newBalance, err := weth9.BalanceOf(&bind.CallOpts{}, aliceOpts.From) - require.NoError(t, err) - require.Equal(t, newBalance, big.NewInt(10_000_000)) - - // Function to prepare and make call to depositERC20Transaction and make - // appropriate assertions dependent on whether custom gas tokens have been enabled or not. - checkDeposit := func(t *testing.T, enabled bool) { - // Set amount of WETH9 to bridge to the recipient on L2 - amountToBridge := big.NewInt(10) - recipient := common.HexToAddress("0xbeefdead") - - // Approve OptimismPortal - tx, err = weth9.Approve(aliceOpts, cfg.L1Deployments.OptimismPortalProxy, amountToBridge) - waitForTx(t, tx, err, l1Client) - - // Get recipient L2 balance before bridging - previousL2Balance, err := l2Client.BalanceAt(context.Background(), recipient, nil) - require.NoError(t, err) - - // Bridge the tokens - optimismPortal, err := bindings.NewOptimismPortal(cfg.L1Deployments.OptimismPortalProxy, l1Client) - require.NoError(t, err) - tx, err = optimismPortal.DepositERC20Transaction(aliceOpts, - recipient, - amountToBridge, - amountToBridge, - 50_0000, // _gasLimit - false, - []byte{}, - ) - if enabled { - require.NoError(t, err) - receipt, err := wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) - require.NoError(t, err) - - // compute the deposit transaction hash + poll for it - depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) - require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) - require.NoError(t, err) - _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) - require.NoError(t, err) - - require.EventuallyWithT(t, func(t *assert.CollectT) { - // check for balance increase on L2 - newL2Balance, err := l2Client.BalanceAt(context.Background(), recipient, nil) - require.NoError(t, err) - l2BalanceIncrease := big.NewInt(0).Sub(newL2Balance, previousL2Balance) - require.Equal(t, amountToBridge, l2BalanceIncrease) - }, 10*time.Second, 1*time.Second) - } else { - require.Error(t, err) - } - } - - // Function to prepare and execute withdrawal flow for CGTs - // and assert token balance is increased on L1. - checkWithdrawal := func(t *testing.T) { - l2Seq := l2Client - l2Verif := sys.NodeClient("verifier") - fromAddr := aliceOpts.From - ethPrivKey := cfg.Secrets.Alice - - // Start L2 balance for withdrawal - startBalanceBeforeWithdrawal, err := l2Seq.BalanceAt(context.Background(), fromAddr, nil) - require.NoError(t, err) - - withdrawAmount := big.NewInt(5) - tx, receipt := helpers.SendWithdrawal(t, cfg, l2Seq, cfg.Secrets.Alice, func(opts *helpers.WithdrawalTxOpts) { - opts.Value = withdrawAmount - opts.VerifyOnClients(l2Verif) - }) - - // Verify L2 balance after withdrawal - header, err := l2Verif.HeaderByNumber(context.Background(), receipt.BlockNumber) - require.NoError(t, err) - - endBalanceAfterWithdrawal, err := wait.ForBalanceChange(context.Background(), l2Seq, fromAddr, startBalanceBeforeWithdrawal) - require.NoError(t, err) - - // Take fee into account - diff := new(big.Int).Sub(startBalanceBeforeWithdrawal, endBalanceAfterWithdrawal) - fees := helpers.CalcGasFees(receipt.GasUsed, tx.GasTipCap(), tx.GasFeeCap(), header.BaseFee) - fees = fees.Add(fees, receipt.L1Fee) - diff = diff.Sub(diff, fees) - require.Equal(t, withdrawAmount, diff) - - // Take start token balance on L1 - startTokenBalanceBeforeFinalize, err := weth9.BalanceOf(&bind.CallOpts{}, fromAddr) - require.NoError(t, err) - - startETHBalanceBeforeFinalize, err := l1Client.BalanceAt(context.Background(), fromAddr, nil) - require.NoError(t, err) - - proveReceipt, finalizeReceipt, resolveClaimReceipt, resolveReceipt := helpers.ProveAndFinalizeWithdrawal(t, cfg, sys, "verifier", ethPrivKey, receipt) - - // Verify L1 ETH balance change - proveFee := new(big.Int).Mul(new(big.Int).SetUint64(proveReceipt.GasUsed), proveReceipt.EffectiveGasPrice) - finalizeFee := new(big.Int).Mul(new(big.Int).SetUint64(finalizeReceipt.GasUsed), finalizeReceipt.EffectiveGasPrice) - fees = new(big.Int).Add(proveFee, finalizeFee) - if allocType.UsesProofs() { - resolveClaimFee := new(big.Int).Mul(new(big.Int).SetUint64(resolveClaimReceipt.GasUsed), resolveClaimReceipt.EffectiveGasPrice) - resolveFee := new(big.Int).Mul(new(big.Int).SetUint64(resolveReceipt.GasUsed), resolveReceipt.EffectiveGasPrice) - fees = new(big.Int).Add(fees, resolveClaimFee) - fees = new(big.Int).Add(fees, resolveFee) - } - - // Verify L1ETHBalance after withdrawal - // On CGT chains, the only change in ETH balance from a withdrawal - // is a decrease to pay for gas - endETHBalanceAfterFinalize, err := l1Client.BalanceAt(context.Background(), fromAddr, nil) - require.NoError(t, err) - diff = new(big.Int).Sub(endETHBalanceAfterFinalize, startETHBalanceBeforeFinalize) - require.Equal(t, new(big.Int).Sub(big.NewInt(0), fees), diff) - - // Verify token balance after withdrawal - // L1 Fees are paid in ETH, and - // withdrawal is of a Custom Gas Token, so we do not subtract l1 fees from expected balance change - // as we would if ETH was the gas paying token - endTokenBalanceAfterFinalize, err := weth9.BalanceOf(&bind.CallOpts{}, fromAddr) - require.NoError(t, err) - diff = new(big.Int).Sub(endTokenBalanceAfterFinalize, startTokenBalanceBeforeFinalize) - require.Equal(t, withdrawAmount, diff) - } - - // checkFeeWithdrawal ensures that the FeeVault can be withdrawn from - checkFeeWithdrawal := func(t *testing.T, enabled bool) { - feeVault, err := bindings.NewSequencerFeeVault(predeploys.SequencerFeeVaultAddr, l2Client) - require.NoError(t, err) - - // Alice will be sending transactions - aliceOpts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L2ChainIDBig()) - require.NoError(t, err) - - // Get the recipient of the funds - recipient, err := feeVault.RECIPIENT(&bind.CallOpts{}) - require.NoError(t, err) - - // This test depends on the withdrawal network being L1 which is represented - // by 0 in the enum. - withdrawalNetwork, err := feeVault.WITHDRAWALNETWORK(&bind.CallOpts{}) - require.NoError(t, err) - require.Equal(t, withdrawalNetwork, uint8(0)) - - // Get the balance of the recipient on L1 - var recipientBalanceBefore *big.Int - if enabled { - recipientBalanceBefore, err = weth9.BalanceOf(&bind.CallOpts{}, recipient) - } else { - recipientBalanceBefore, err = l1Client.BalanceAt(context.Background(), recipient, nil) - } - require.NoError(t, err) - - // Get the min withdrawal amount for the FeeVault - amount, err := feeVault.MINWITHDRAWALAMOUNT(&bind.CallOpts{}) - require.NoError(t, err) - - l1opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L1ChainIDBig()) - require.NoError(t, err) - - optimismPortal, err := bindings.NewOptimismPortal(cfg.L1Deployments.OptimismPortalProxy, l1Client) - require.NoError(t, err) - - depositAmount := new(big.Int).Mul(amount, big.NewInt(14)) - l1opts.Value = depositAmount - - var receipt *types.Receipt - - // Alice deposits funds - if enabled { - // approve + transferFrom flow - // Cannot use `transfer` because of the tracking of balance in the OptimismPortal - dep, err := weth9.Deposit(l1opts) - waitForTx(t, dep, err, l1Client) - - l1opts.Value = nil - tx, err := weth9.Approve(l1opts, cfg.L1Deployments.OptimismPortalProxy, depositAmount) - waitForTx(t, tx, err, l1Client) - - require.NoError(t, err) - deposit, err := optimismPortal.DepositERC20Transaction(l1opts, cfg.Secrets.Addresses().Alice, depositAmount, depositAmount, 500_000, false, []byte{}) - waitForTx(t, deposit, err, l1Client) - - receipt, err = wait.ForReceiptOK(context.Background(), l1Client, deposit.Hash()) - require.NoError(t, err) - } else { - // send ether to the portal directly, alice already has funds on L2 - tx, err := optimismPortal.DepositTransaction(l1opts, cfg.Secrets.Addresses().Alice, depositAmount, 500_000, false, []byte{}) - waitForTx(t, tx, err, l1Client) - - receipt, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) - require.NoError(t, err) - } - - // Compute the deposit transaction hash + poll for it - depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) - require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) - require.NoError(t, err) - _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) - require.NoError(t, err) - - // Get Alice's balance on L2 - aliceBalance, err := l2Client.BalanceAt(context.Background(), cfg.Secrets.Addresses().Alice, nil) - require.NoError(t, err) - require.GreaterOrEqual(t, aliceBalance.Uint64(), amount.Uint64()) - - // Send funds to the FeeVault so its balance is above the min withdrawal amount - aliceOpts.Value = amount - feeVaultTx, err := feeVault.Receive(aliceOpts) - waitForTx(t, feeVaultTx, err, l2Client) - - // Ensure that the balance of the vault is large enough to withdraw - vaultBalance, err := l2Client.BalanceAt(context.Background(), predeploys.SequencerFeeVaultAddr, nil) - require.NoError(t, err) - require.GreaterOrEqual(t, vaultBalance.Uint64(), amount.Uint64()) - - // Ensure there is code at the vault address - code, err := l2Client.CodeAt(context.Background(), predeploys.SequencerFeeVaultAddr, nil) - require.NoError(t, err) - require.NotEmpty(t, code) - - // Poke the fee vault to withdraw - l2Opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Bob, cfg.L2ChainIDBig()) - require.NoError(t, err) - withdrawalTx, err := feeVault.Withdraw(l2Opts) - waitForTx(t, withdrawalTx, err, l2Client) - - // Get the receipt and the amount withdrawn - receipt, err = l2Client.TransactionReceipt(context.Background(), withdrawalTx.Hash()) - require.NoError(t, err) - - inclusionHeight := receipt.BlockNumber.Uint64() - it, err := feeVault.FilterWithdrawal(&bind.FilterOpts{ - Start: inclusionHeight, - End: &inclusionHeight, - }) - require.NoError(t, err) - require.True(t, it.Next()) - - withdrawnAmount := it.Event.Value - - // Finalize the withdrawal - proveReceipt, finalizeReceipt, resolveClaimReceipt, resolveReceipt := helpers.ProveAndFinalizeWithdrawal(t, cfg, sys, "verifier", cfg.Secrets.Alice, receipt) - require.Equal(t, types.ReceiptStatusSuccessful, proveReceipt.Status) - require.Equal(t, types.ReceiptStatusSuccessful, finalizeReceipt.Status) - if allocType.UsesProofs() { - require.Equal(t, types.ReceiptStatusSuccessful, resolveClaimReceipt.Status) - require.Equal(t, types.ReceiptStatusSuccessful, resolveReceipt.Status) - } - - // Assert that the recipient's balance did increase - var recipientBalanceAfter *big.Int - if enabled { - recipientBalanceAfter, err = weth9.BalanceOf(&bind.CallOpts{}, recipient) - } else { - recipientBalanceAfter, err = l1Client.BalanceAt(context.Background(), recipient, nil) - } - require.NoError(t, err) - - require.Equal(t, recipientBalanceAfter, new(big.Int).Add(recipientBalanceBefore, withdrawnAmount)) - } - - checkL1TokenNameAndSymbol := func(t *testing.T, enabled bool) { - systemConfig, err := bindings.NewSystemConfig(cfg.L1Deployments.SystemConfigProxy, l1Client) - require.NoError(t, err) - - token, err := systemConfig.GasPayingToken(&bind.CallOpts{}) - require.NoError(t, err) - - name, err := systemConfig.GasPayingTokenName(&bind.CallOpts{}) - require.NoError(t, err) - - symbol, err := systemConfig.GasPayingTokenSymbol(&bind.CallOpts{}) - require.NoError(t, err) - if enabled { - require.Equal(t, enabledExpectations.tokenAddress, token.Addr) - require.Equal(t, enabledExpectations.tokenDecimals, token.Decimals) - require.Equal(t, enabledExpectations.tokenName, name) - require.Equal(t, enabledExpectations.tokenSymbol, symbol) - } else { - require.Equal(t, disabledExpectations.tokenAddress, token.Addr) - require.Equal(t, disabledExpectations.tokenDecimals, token.Decimals) - require.Equal(t, disabledExpectations.tokenName, name) - require.Equal(t, disabledExpectations.tokenSymbol, symbol) - } - } + setup := func(t *testing.T) gasTokenTestOpts { + cfg := e2esys.DefaultSystemConfig(t, e2esys.WithAllocType(allocType)) + offset := hexutil.Uint64(0) + cfg.DeployConfig.L2GenesisRegolithTimeOffset = &offset + cfg.DeployConfig.L1CancunTimeOffset = &offset + cfg.DeployConfig.L2GenesisCanyonTimeOffset = &offset + cfg.DeployConfig.L2GenesisDeltaTimeOffset = &offset + cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &offset - checkL2TokenNameAndSymbol := func(t *testing.T, enabled bool) { - l1Block, err := bindings.NewL1Block(predeploys.L1BlockAddr, l2Client) - require.NoError(t, err) + sys, err := cfg.Start(t) + require.NoError(t, err, "Error starting up system") - token, err := l1Block.GasPayingToken(&bind.CallOpts{}) + l1Client := sys.NodeClient("l1") + aliceOpts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L1ChainIDBig()) require.NoError(t, err) - name, err := l1Block.GasPayingTokenName(&bind.CallOpts{}) + // Deploy WETH9, we'll use this as our custom gas token for the purpose of the test + weth9Address, tx, weth9, err := bindings.DeployWETH9(aliceOpts, l1Client) require.NoError(t, err) - - symbol, err := l1Block.GasPayingTokenSymbol(&bind.CallOpts{}) + _, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) require.NoError(t, err) - if enabled { - require.Equal(t, enabledExpectations.tokenAddress, token.Addr) - require.Equal(t, enabledExpectations.tokenDecimals, token.Decimals) - require.Equal(t, enabledExpectations.tokenName, name) - require.Equal(t, enabledExpectations.tokenSymbol, symbol) - } else { - require.Equal(t, disabledExpectations.tokenAddress, token.Addr) - require.Equal(t, disabledExpectations.tokenDecimals, token.Decimals) - require.Equal(t, disabledExpectations.tokenName, name) - require.Equal(t, disabledExpectations.tokenSymbol, symbol) - } - } - - checkWETHTokenNameAndSymbol := func(t *testing.T, enabled bool) { - // Check name and symbol in WETH predeploy - weth, err := bindings.NewWETH(predeploys.WETHAddr, l2Client) + enabledExpectations := cgtTestExpectations{} + enabledExpectations.tokenAddress = weth9Address + enabledExpectations.tokenName, err = weth9.Name(&bind.CallOpts{}) require.NoError(t, err) - - name, err := weth.Name(&bind.CallOpts{}) + enabledExpectations.tokenSymbol, err = weth9.Symbol(&bind.CallOpts{}) require.NoError(t, err) - - symbol, err := weth.Symbol(&bind.CallOpts{}) + enabledExpectations.tokenDecimals, err = weth9.Decimals(&bind.CallOpts{}) require.NoError(t, err) - if enabled { - require.Equal(t, "Wrapped "+enabledExpectations.tokenName, name) - require.Equal(t, "W"+enabledExpectations.tokenSymbol, symbol) - } else { - require.Equal(t, "Wrapped "+disabledExpectations.tokenName, name) - require.Equal(t, "W"+disabledExpectations.tokenSymbol, symbol) + // Get some WETH + aliceOpts.Value = big.NewInt(10_000_000) + tx, err = weth9.Deposit(aliceOpts) + waitForTx(t, tx, err, l1Client) + aliceOpts.Value = nil + newBalance, err := weth9.BalanceOf(&bind.CallOpts{}, aliceOpts.From) + require.NoError(t, err) + require.Equal(t, newBalance, big.NewInt(10_000_000)) + + return gasTokenTestOpts{ + aliceOpts: aliceOpts, + cfg: cfg, + weth9: weth9, + weth9Address: weth9Address, + allocType: allocType, + sys: sys, + enabledExpectations: enabledExpectations, + disabledExpectations: disabledExpectations, } } - // Begin by testing behaviour when CGT feature is not enabled - enabled := false - checkDeposit(t, enabled) - checkL1TokenNameAndSymbol(t, enabled) - checkL2TokenNameAndSymbol(t, enabled) - checkWETHTokenNameAndSymbol(t, enabled) - checkFeeWithdrawal(t, enabled) - - // Activate custom gas token feature (devnet does not have this activated at genesis) - setCustomGasToken(t, cfg, sys, weth9Address) - - // Now test behaviour given CGT feature is enabled - enabled = true - checkDeposit(t, enabled) - checkWithdrawal(t) - checkL1TokenNameAndSymbol(t, enabled) - checkL2TokenNameAndSymbol(t, enabled) - checkWETHTokenNameAndSymbol(t, enabled) - checkFeeWithdrawal(t, enabled) + t.Run("deposit", func(t *testing.T) { + op_e2e.InitParallel(t) + gto := setup(t) + checkDeposit(t, gto, false) + setCustomGasToken(t, gto.cfg, gto.sys, gto.weth9Address) + checkDeposit(t, gto, true) + }) + + t.Run("withdrawal", func(t *testing.T) { + op_e2e.InitParallel(t) + gto := setup(t) + setCustomGasToken(t, gto.cfg, gto.sys, gto.weth9Address) + checkDeposit(t, gto, true) + checkWithdrawal(t, gto) + }) + + t.Run("fee withdrawal", func(t *testing.T) { + op_e2e.InitParallel(t) + gto := setup(t) + setCustomGasToken(t, gto.cfg, gto.sys, gto.weth9Address) + checkDeposit(t, gto, true) + checkFeeWithdrawal(t, gto, true) + }) + + t.Run("token name and symbol", func(t *testing.T) { + op_e2e.InitParallel(t) + gto := setup(t) + checkL1TokenNameAndSymbol(t, gto, gto.disabledExpectations) + checkL2TokenNameAndSymbol(t, gto, gto.disabledExpectations) + checkWETHTokenNameAndSymbol(t, gto, gto.disabledExpectations) + setCustomGasToken(t, gto.cfg, gto.sys, gto.weth9Address) + checkL1TokenNameAndSymbol(t, gto, gto.enabledExpectations) + checkL2TokenNameAndSymbol(t, gto, gto.enabledExpectations) + checkWETHTokenNameAndSymbol(t, gto, gto.enabledExpectations) + }) } -// setCustomGasToeken enables the Custom Gas Token feature on a chain where it wasn't enabled at genesis. +// setCustomGasToken enables the Custom Gas Token feature on a chain where it wasn't enabled at genesis. // It reads existing parameters from the SystemConfig contract, inserts the supplied cgtAddress and reinitializes that contract. // To do this it uses the ProxyAdmin and StorageSetter from the supplied cfg. func setCustomGasToken(t *testing.T, cfg e2esys.SystemConfig, sys *e2esys.System, cgtAddress common.Address) { @@ -571,3 +270,350 @@ func waitForTx(t *testing.T, tx *types.Transaction, err error, client *ethclient _, err = wait.ForReceiptOK(context.Background(), client, tx.Hash()) require.NoError(t, err) } + +type gasTokenTestOpts struct { + aliceOpts *bind.TransactOpts + cfg e2esys.SystemConfig + weth9 *bindings.WETH9 + weth9Address common.Address + allocType config.AllocType + sys *e2esys.System + enabledExpectations cgtTestExpectations + disabledExpectations cgtTestExpectations +} + +// Function to prepare and make call to depositERC20Transaction and make +// appropriate assertions dependent on whether custom gas tokens have been enabled or not. +func checkDeposit(t *testing.T, gto gasTokenTestOpts, enabled bool) { + aliceOpts := gto.aliceOpts + cfg := gto.cfg + l1Client := gto.sys.NodeClient("l1") + l2Client := gto.sys.NodeClient("sequencer") + weth9 := gto.weth9 + + // Set amount of WETH9 to bridge to the recipient on L2 + amountToBridge := big.NewInt(10) + recipient := common.HexToAddress("0xbeefdead") + + // Approve OptimismPortal + tx, err := weth9.Approve(aliceOpts, cfg.L1Deployments.OptimismPortalProxy, amountToBridge) + waitForTx(t, tx, err, l1Client) + + // Get recipient L2 balance before bridging + previousL2Balance, err := l2Client.BalanceAt(context.Background(), recipient, nil) + require.NoError(t, err) + + // Bridge the tokens + optimismPortal, err := bindings.NewOptimismPortal(cfg.L1Deployments.OptimismPortalProxy, l1Client) + require.NoError(t, err) + tx, err = optimismPortal.DepositERC20Transaction(aliceOpts, + recipient, + amountToBridge, + amountToBridge, + 50_0000, // _gasLimit + false, + []byte{}, + ) + if enabled { + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) + require.NoError(t, err) + + // compute the deposit transaction hash + poll for it + depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) + require.NoError(t, err, "Should emit deposit event") + depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + require.NoError(t, err) + _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) + require.NoError(t, err) + + require.EventuallyWithT(t, func(t *assert.CollectT) { + // check for balance increase on L2 + newL2Balance, err := l2Client.BalanceAt(context.Background(), recipient, nil) + require.NoError(t, err) + l2BalanceIncrease := big.NewInt(0).Sub(newL2Balance, previousL2Balance) + require.Equal(t, amountToBridge, l2BalanceIncrease) + }, 10*time.Second, 1*time.Second) + } else { + require.Error(t, err) + } +} + +// Function to prepare and execute withdrawal flow for CGTs +// and assert token balance is increased on L1. +func checkWithdrawal(t *testing.T, gto gasTokenTestOpts) { + aliceOpts := gto.aliceOpts + cfg := gto.cfg + weth9 := gto.weth9 + allocType := gto.allocType + l1Client := gto.sys.NodeClient("l1") + l2Seq := gto.sys.NodeClient("sequencer") + l2Verif := gto.sys.NodeClient("verifier") + fromAddr := aliceOpts.From + ethPrivKey := cfg.Secrets.Alice + + // Start L2 balance for withdrawal + startBalanceBeforeWithdrawal, err := l2Seq.BalanceAt(context.Background(), fromAddr, nil) + require.NoError(t, err) + + withdrawAmount := big.NewInt(5) + tx, receipt := helpers.SendWithdrawal(t, cfg, l2Seq, cfg.Secrets.Alice, func(opts *helpers.WithdrawalTxOpts) { + opts.Value = withdrawAmount + opts.VerifyOnClients(l2Verif) + }) + + // Verify L2 balance after withdrawal + header, err := l2Verif.HeaderByNumber(context.Background(), receipt.BlockNumber) + require.NoError(t, err) + + endBalanceAfterWithdrawal, err := wait.ForBalanceChange(context.Background(), l2Seq, fromAddr, startBalanceBeforeWithdrawal) + require.NoError(t, err) + + // Take fee into account + diff := new(big.Int).Sub(startBalanceBeforeWithdrawal, endBalanceAfterWithdrawal) + fees := helpers.CalcGasFees(receipt.GasUsed, tx.GasTipCap(), tx.GasFeeCap(), header.BaseFee) + fees = fees.Add(fees, receipt.L1Fee) + diff = diff.Sub(diff, fees) + require.Equal(t, withdrawAmount, diff) + + // Take start token balance on L1 + startTokenBalanceBeforeFinalize, err := weth9.BalanceOf(&bind.CallOpts{}, fromAddr) + require.NoError(t, err) + + startETHBalanceBeforeFinalize, err := l1Client.BalanceAt(context.Background(), fromAddr, nil) + require.NoError(t, err) + + proveReceipt, finalizeReceipt, resolveClaimReceipt, resolveReceipt := helpers.ProveAndFinalizeWithdrawal(t, cfg, gto.sys, "verifier", ethPrivKey, receipt) + + // Verify L1 ETH balance change + proveFee := new(big.Int).Mul(new(big.Int).SetUint64(proveReceipt.GasUsed), proveReceipt.EffectiveGasPrice) + finalizeFee := new(big.Int).Mul(new(big.Int).SetUint64(finalizeReceipt.GasUsed), finalizeReceipt.EffectiveGasPrice) + fees = new(big.Int).Add(proveFee, finalizeFee) + if allocType.UsesProofs() { + resolveClaimFee := new(big.Int).Mul(new(big.Int).SetUint64(resolveClaimReceipt.GasUsed), resolveClaimReceipt.EffectiveGasPrice) + resolveFee := new(big.Int).Mul(new(big.Int).SetUint64(resolveReceipt.GasUsed), resolveReceipt.EffectiveGasPrice) + fees = new(big.Int).Add(fees, resolveClaimFee) + fees = new(big.Int).Add(fees, resolveFee) + } + + // Verify L1ETHBalance after withdrawal + // On CGT chains, the only change in ETH balance from a withdrawal + // is a decrease to pay for gas + endETHBalanceAfterFinalize, err := l1Client.BalanceAt(context.Background(), fromAddr, nil) + require.NoError(t, err) + diff = new(big.Int).Sub(endETHBalanceAfterFinalize, startETHBalanceBeforeFinalize) + require.Equal(t, new(big.Int).Sub(big.NewInt(0), fees), diff) + + // Verify token balance after withdrawal + // L1 Fees are paid in ETH, and + // withdrawal is of a Custom Gas Token, so we do not subtract l1 fees from expected balance change + // as we would if ETH was the gas paying token + endTokenBalanceAfterFinalize, err := weth9.BalanceOf(&bind.CallOpts{}, fromAddr) + require.NoError(t, err) + diff = new(big.Int).Sub(endTokenBalanceAfterFinalize, startTokenBalanceBeforeFinalize) + require.Equal(t, withdrawAmount, diff) +} + +// checkFeeWithdrawal ensures that the FeeVault can be withdrawn from +func checkFeeWithdrawal(t *testing.T, gto gasTokenTestOpts, enabled bool) { + cfg := gto.cfg + weth9 := gto.weth9 + allocType := gto.allocType + l1Client := gto.sys.NodeClient("l1") + l2Client := gto.sys.NodeClient("sequencer") + + feeVault, err := bindings.NewSequencerFeeVault(predeploys.SequencerFeeVaultAddr, l2Client) + require.NoError(t, err) + + // Alice will be sending transactions + aliceOpts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L2ChainIDBig()) + require.NoError(t, err) + + // Get the recipient of the funds + recipient, err := feeVault.RECIPIENT(&bind.CallOpts{}) + require.NoError(t, err) + + // This test depends on the withdrawal network being L1 which is represented + // by 0 in the enum. + withdrawalNetwork, err := feeVault.WITHDRAWALNETWORK(&bind.CallOpts{}) + require.NoError(t, err) + require.Equal(t, withdrawalNetwork, uint8(0)) + + // Get the balance of the recipient on L1 + var recipientBalanceBefore *big.Int + if enabled { + recipientBalanceBefore, err = weth9.BalanceOf(&bind.CallOpts{}, recipient) + } else { + recipientBalanceBefore, err = l1Client.BalanceAt(context.Background(), recipient, nil) + } + require.NoError(t, err) + + // Get the min withdrawal amount for the FeeVault + amount, err := feeVault.MINWITHDRAWALAMOUNT(&bind.CallOpts{}) + require.NoError(t, err) + + l1opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L1ChainIDBig()) + require.NoError(t, err) + + optimismPortal, err := bindings.NewOptimismPortal(cfg.L1Deployments.OptimismPortalProxy, l1Client) + require.NoError(t, err) + + depositAmount := new(big.Int).Mul(amount, big.NewInt(14)) + l1opts.Value = depositAmount + + var receipt *types.Receipt + + // Alice deposits funds + if enabled { + // approve + transferFrom flow + // Cannot use `transfer` because of the tracking of balance in the OptimismPortal + dep, err := weth9.Deposit(l1opts) + waitForTx(t, dep, err, l1Client) + + l1opts.Value = nil + tx, err := weth9.Approve(l1opts, cfg.L1Deployments.OptimismPortalProxy, depositAmount) + waitForTx(t, tx, err, l1Client) + + require.NoError(t, err) + deposit, err := optimismPortal.DepositERC20Transaction(l1opts, cfg.Secrets.Addresses().Alice, depositAmount, depositAmount, 500_000, false, []byte{}) + waitForTx(t, deposit, err, l1Client) + + receipt, err = wait.ForReceiptOK(context.Background(), l1Client, deposit.Hash()) + require.NoError(t, err) + } else { + // send ether to the portal directly, alice already has funds on L2 + tx, err := optimismPortal.DepositTransaction(l1opts, cfg.Secrets.Addresses().Alice, depositAmount, 500_000, false, []byte{}) + waitForTx(t, tx, err, l1Client) + + receipt, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) + require.NoError(t, err) + } + + // Compute the deposit transaction hash + poll for it + depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) + require.NoError(t, err, "Should emit deposit event") + depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + require.NoError(t, err) + _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) + require.NoError(t, err) + + // Get Alice's balance on L2 + aliceBalance, err := l2Client.BalanceAt(context.Background(), cfg.Secrets.Addresses().Alice, nil) + require.NoError(t, err) + require.GreaterOrEqual(t, aliceBalance.Uint64(), amount.Uint64()) + + // Send funds to the FeeVault so its balance is above the min withdrawal amount + aliceOpts.Value = amount + feeVaultTx, err := feeVault.Receive(aliceOpts) + waitForTx(t, feeVaultTx, err, l2Client) + + // Ensure that the balance of the vault is large enough to withdraw + vaultBalance, err := l2Client.BalanceAt(context.Background(), predeploys.SequencerFeeVaultAddr, nil) + require.NoError(t, err) + require.GreaterOrEqual(t, vaultBalance.Uint64(), amount.Uint64()) + + // Ensure there is code at the vault address + code, err := l2Client.CodeAt(context.Background(), predeploys.SequencerFeeVaultAddr, nil) + require.NoError(t, err) + require.NotEmpty(t, code) + + // Poke the fee vault to withdraw + l2Opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Bob, cfg.L2ChainIDBig()) + require.NoError(t, err) + withdrawalTx, err := feeVault.Withdraw(l2Opts) + waitForTx(t, withdrawalTx, err, l2Client) + + // Get the receipt and the amount withdrawn + receipt, err = l2Client.TransactionReceipt(context.Background(), withdrawalTx.Hash()) + require.NoError(t, err) + + inclusionHeight := receipt.BlockNumber.Uint64() + it, err := feeVault.FilterWithdrawal(&bind.FilterOpts{ + Start: inclusionHeight, + End: &inclusionHeight, + }) + require.NoError(t, err) + require.True(t, it.Next()) + + withdrawnAmount := it.Event.Value + + // Finalize the withdrawal + proveReceipt, finalizeReceipt, resolveClaimReceipt, resolveReceipt := helpers.ProveAndFinalizeWithdrawal(t, cfg, gto.sys, "verifier", cfg.Secrets.Alice, receipt) + require.Equal(t, types.ReceiptStatusSuccessful, proveReceipt.Status) + require.Equal(t, types.ReceiptStatusSuccessful, finalizeReceipt.Status) + if allocType.UsesProofs() { + require.Equal(t, types.ReceiptStatusSuccessful, resolveClaimReceipt.Status) + require.Equal(t, types.ReceiptStatusSuccessful, resolveReceipt.Status) + } + + // Assert that the recipient's balance did increase + var recipientBalanceAfter *big.Int + if enabled { + recipientBalanceAfter, err = weth9.BalanceOf(&bind.CallOpts{}, recipient) + } else { + recipientBalanceAfter, err = l1Client.BalanceAt(context.Background(), recipient, nil) + } + require.NoError(t, err) + + require.Equal(t, recipientBalanceAfter, new(big.Int).Add(recipientBalanceBefore, withdrawnAmount)) +} + +func checkL1TokenNameAndSymbol(t *testing.T, gto gasTokenTestOpts, expectations cgtTestExpectations) { + l1Client := gto.sys.NodeClient("l1") + cfg := gto.cfg + + systemConfig, err := bindings.NewSystemConfig(cfg.L1Deployments.SystemConfigProxy, l1Client) + require.NoError(t, err) + + token, err := systemConfig.GasPayingToken(&bind.CallOpts{}) + require.NoError(t, err) + + name, err := systemConfig.GasPayingTokenName(&bind.CallOpts{}) + require.NoError(t, err) + + symbol, err := systemConfig.GasPayingTokenSymbol(&bind.CallOpts{}) + require.NoError(t, err) + + require.Equal(t, expectations.tokenAddress, token.Addr) + require.Equal(t, expectations.tokenDecimals, token.Decimals) + require.Equal(t, expectations.tokenName, name) + require.Equal(t, expectations.tokenSymbol, symbol) +} + +func checkL2TokenNameAndSymbol(t *testing.T, gto gasTokenTestOpts, enabledExpectations cgtTestExpectations) { + l2Client := gto.sys.NodeClient("sequencer") + + l1Block, err := bindings.NewL1Block(predeploys.L1BlockAddr, l2Client) + require.NoError(t, err) + + token, err := l1Block.GasPayingToken(&bind.CallOpts{}) + require.NoError(t, err) + + name, err := l1Block.GasPayingTokenName(&bind.CallOpts{}) + require.NoError(t, err) + + symbol, err := l1Block.GasPayingTokenSymbol(&bind.CallOpts{}) + require.NoError(t, err) + + require.Equal(t, enabledExpectations.tokenAddress, token.Addr) + require.Equal(t, enabledExpectations.tokenDecimals, token.Decimals) + require.Equal(t, enabledExpectations.tokenName, name) + require.Equal(t, enabledExpectations.tokenSymbol, symbol) +} + +func checkWETHTokenNameAndSymbol(t *testing.T, gto gasTokenTestOpts, expectations cgtTestExpectations) { + l2Client := gto.sys.NodeClient("sequencer") + + // Check name and symbol in WETH predeploy + weth, err := bindings.NewWETH(predeploys.WETHAddr, l2Client) + require.NoError(t, err) + + name, err := weth.Name(&bind.CallOpts{}) + require.NoError(t, err) + + symbol, err := weth.Symbol(&bind.CallOpts{}) + require.NoError(t, err) + + require.Equal(t, "Wrapped "+expectations.tokenName, name) + require.Equal(t, "W"+expectations.tokenSymbol, symbol) +} diff --git a/op-e2e/system/helpers/tx_helper.go b/op-e2e/system/helpers/tx_helper.go index f5cb11aa8a1d2..10c16c0e74656 100644 --- a/op-e2e/system/helpers/tx_helper.go +++ b/op-e2e/system/helpers/tx_helper.go @@ -28,7 +28,9 @@ import ( // Returns the receipt of the L2 transaction func SendDepositTx(t *testing.T, cfg e2esys.SystemConfig, l1Client *ethclient.Client, l2Client *ethclient.Client, l1Opts *bind.TransactOpts, applyL2Opts DepositTxOptsFn) *types.Receipt { l2Opts := defaultDepositTxOpts(l1Opts) - applyL2Opts(l2Opts) + if applyL2Opts != nil { + applyL2Opts(l2Opts) + } // Find deposit contract depositContract, err := bindings.NewOptimismPortal(cfg.L1Deployments.OptimismPortalProxy, l1Client) diff --git a/op-e2e/system/p2p/txpool_test.go b/op-e2e/system/p2p/txpool_test.go index dde89ecdefb11..bb5b3e4ef339c 100644 --- a/op-e2e/system/p2p/txpool_test.go +++ b/op-e2e/system/p2p/txpool_test.go @@ -3,7 +3,6 @@ package p2p import ( "math/big" "testing" - "time" op_e2e "github.com/ethereum-optimism/optimism/op-e2e" @@ -31,7 +30,7 @@ func TestTxGossip(t *testing.T) { geth.ConnectP2P(t, seqClient, verifClient) // This prevents the below tx-sending from flaking in CI - _, err = geth.WaitForBlock(big.NewInt(10), verifClient, time.Minute) + _, err = geth.WaitForBlock(big.NewInt(10), verifClient) require.NoError(t, err) // Send a transaction to the verifier and it should be gossiped to the sequencer and included in a block. diff --git a/op-e2e/system/proofs/build_helper.go b/op-e2e/system/proofs/build_helper.go index 42201279867b0..9bad76b9dcb18 100644 --- a/op-e2e/system/proofs/build_helper.go +++ b/op-e2e/system/proofs/build_helper.go @@ -2,7 +2,9 @@ package proofs import ( "context" + "os" "os/exec" + "path/filepath" "strings" "testing" "time" @@ -12,6 +14,15 @@ import ( // BuildOpProgramClient builds the `op-program` client executable and returns the path to the resulting executable func BuildOpProgramClient(t *testing.T) string { + clientPath, err := filepath.Abs("../../../op-program/bin/op-program-client") + require.NoError(t, err) + + _, err = os.Stat(clientPath) + if err == nil { + return clientPath + } + require.ErrorIs(t, err, os.ErrNotExist) + t.Log("Building op-program-client") ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() diff --git a/op-e2e/system/proofs/proposer_fp_test.go b/op-e2e/system/proofs/proposer_fp_test.go index 6be17baaaf25c..971ed06a46303 100644 --- a/op-e2e/system/proofs/proposer_fp_test.go +++ b/op-e2e/system/proofs/proposer_fp_test.go @@ -40,7 +40,7 @@ func TestL2OutputSubmitterFaultProofs(t *testing.T) { require.Nil(t, err) l2Verif := sys.NodeClient("verifier") - _, err = geth.WaitForBlock(big.NewInt(6), l2Verif, 10*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) + _, err = geth.WaitForBlock(big.NewInt(6), l2Verif) require.Nil(t, err) timeoutCh := time.After(15 * time.Second) diff --git a/op-e2e/system/proofs/proposer_l2oo_test.go b/op-e2e/system/proofs/proposer_l2oo_test.go index ccc9da0701c00..bf718c616538b 100644 --- a/op-e2e/system/proofs/proposer_l2oo_test.go +++ b/op-e2e/system/proofs/proposer_l2oo_test.go @@ -42,7 +42,7 @@ func TestL2OutputSubmitter(t *testing.T) { // for that block and subsequently reorgs to match what the verifier derives when running the // reconcillation process. l2Verif := sys.NodeClient("verifier") - _, err = geth.WaitForBlock(big.NewInt(6), l2Verif, 10*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) + _, err = geth.WaitForBlock(big.NewInt(6), l2Verif) require.Nil(t, err) // Wait for batch submitter to update L2 output oracle. diff --git a/op-e2e/system/proofs/system_fpp_test.go b/op-e2e/system/proofs/system_fpp_test.go index 8729f2c7055c2..3eb468aaa8fa1 100644 --- a/op-e2e/system/proofs/system_fpp_test.go +++ b/op-e2e/system/proofs/system_fpp_test.go @@ -77,6 +77,7 @@ func applySpanBatchActivation(active bool, dp *genesis.DeployConfig) { dp.L2GenesisDeltaTimeOffset = nil dp.L2GenesisEcotoneTimeOffset = nil dp.L2GenesisFjordTimeOffset = nil + dp.L2GenesisGraniteTimeOffset = nil } } @@ -99,7 +100,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool, spanBatchActi delete(cfg.Nodes, "verifier") // Use a small sequencer window size to avoid test timeout while waiting for empty blocks // But not too small to ensure that our claim and subsequent state change is published - cfg.DeployConfig.SequencerWindowSize = 16 + cfg.DeployConfig.SequencerWindowSize = 30 applySpanBatchActivation(spanBatchActivated, cfg.DeployConfig) sys, err := cfg.Start(t) diff --git a/op-e2e/system/verifier/basic_test.go b/op-e2e/system/verifier/basic_test.go index effe1f4121462..d0791d5ec483d 100644 --- a/op-e2e/system/verifier/basic_test.go +++ b/op-e2e/system/verifier/basic_test.go @@ -71,7 +71,7 @@ func runE2ESystemTest(t *testing.T, sys *e2esys.System) { require.Nil(t, err) mintAmount := big.NewInt(1_000_000_000_000) opts.Value = mintAmount - helpers.SendDepositTx(t, sys.Cfg, l1Client, l2Verif, opts, func(l2Opts *helpers.DepositTxOpts) {}) + helpers.SendDepositTx(t, sys.Cfg, l1Client, l2Verif, opts, nil) // Confirm balance ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second) diff --git a/op-e2e/system/verifier/sequencer_window_test.go b/op-e2e/system/verifier/sequencer_window_test.go index aa836402e99b8..04b88c54da94c 100644 --- a/op-e2e/system/verifier/sequencer_window_test.go +++ b/op-e2e/system/verifier/sequencer_window_test.go @@ -48,7 +48,7 @@ func TestMissingBatchE2E(t *testing.T) { }) // Wait until the block it was first included in shows up in the safe chain on the verifier - _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif, time.Duration((sys.RollupConfig.SeqWindowSize+4)*cfg.DeployConfig.L1BlockTime)*time.Second) + _, err = geth.WaitForBlock(receipt.BlockNumber, l2Verif) require.Nil(t, err, "Waiting for block on verifier") // Assert that the transaction is not found on the verifier diff --git a/op-node/Makefile b/op-node/Makefile index 75b152b2d53ec..b63f489925ac0 100644 --- a/op-node/Makefile +++ b/op-node/Makefile @@ -1,63 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -# Find the github tag that points to this commit. If none are found, set the version string to "untagged" -# Prioritizes release tag, if one exists, over tags suffixed with "-rc" -VERSION ?= $(shell tags=$$(git tag --points-at $(GITCOMMIT) | grep '^op-node/' | sed 's/op-node\///' | sort -V); \ - preferred_tag=$$(echo "$$tags" | grep -v -- '-rc' | tail -n 1); \ - if [ -z "$$preferred_tag" ]; then \ - if [ -z "$$tags" ]; then \ - echo "untagged"; \ - else \ - echo "$$tags" | tail -n 1; \ - fi \ - else \ - echo $$preferred_tag; \ - fi) +DEPRECATED_TARGETS := op-node clean test fuzz generate-mocks readme -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-node/version.Version=$(VERSION) -LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-node/version.Meta=$(VERSION_META) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -# Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169 -ifeq ($(shell uname),Darwin) - FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic -endif - -op-node: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-node ./cmd/main.go - -clean: - rm bin/op-node - -test: - go test -v ./... - -fuzz: - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzL1InfoBedrockRoundTrip ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzL1InfoEcotoneRoundTrip ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzL1InfoAgainstContract ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzUnmarshallLogEvent ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzParseFrames ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzFrameUnmarshalBinary ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzBatchRoundTrip ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDeriveDepositsRoundTrip ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDeriveDepositsBadVersion ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzParseL1InfoDepositTxDataValid ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzParseL1InfoDepositTxDataBadLength ./rollup/derive - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzRejectCreateBlockBadTimestamp ./rollup/driver - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDecodeDepositTxDataToL1Info ./rollup/driver - -generate-mocks: - go generate ./... - -readme: - doctoc README.md - -.PHONY: \ - op-node \ - clean \ - test \ - fuzz \ - readme +include ../just/deprecated.mk diff --git a/op-node/README.md b/op-node/README.md index cfc3793052f13..b9b28fa05f15c 100644 --- a/op-node/README.md +++ b/op-node/README.md @@ -1,105 +1,252 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* +# `op-node` -- [op-node](#op-node) - - [Compiling](#compiling) - - [Testing](#testing) - - [Running](#running) - - [L2 Genesis Generation](#l2-genesis-generation) - - [L1 Devnet Genesis Generation](#l1-devnet-genesis-generation) +Issues: +[monorepo](https://github.com/ethereum-optimism/optimism/issues?q=is%3Aissue%20state%3Aopen%20label%3AA-op-node) - +Pull requests: +[monorepo](https://github.com/ethereum-optimism/optimism/pulls?q=is%3Aopen+is%3Apr+label%3AA-op-node) -# op-node +User docs: +- [How to run a node](https://docs.optimism.io/builders/node-operators/rollup-node) -This is the reference implementation of the [rollup-node spec](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/rollup-node.md). -It can be thought of like the consensus layer client of an OP Stack chain where it must run with an OP Stack execution layer client -like [op-geth](https://github.com/ethereum-optimism/op-geth). +Specs: +- [rollup-node spec] -## Compiling +The op-node implements the [rollup-node spec]. +It functions as a Consensus Layer client of an OP Stack chain. +This builds, relays and verifies the canonical chain of blocks. +The blocks are processed by an execution layer client, like [op-geth]. -Compile a binary: +[rollup-node spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/rollup-node.m +[op-geth]: https://github.com/ethereum-optimism/op-geth -```shell +## Quickstart + +```bash make op-node + +# Network selection: +# - Join any of the pre-configured networks with the `--network` flag. +# - Alternatively, join a custom network with the `--rollup.config` flag. +# +# Essential Connections: +# - L1 ethereum RPC, to fetch blocks, receipts, finality +# - L1 beacon API, to fetch blobs +# - L2 engine API, to apply new blocks to +# - P2P TCP port, to expose publicly, to retrieve and relay the latest L2 blocks +# - P2P UDP port, to expose publicly, to discover other nodes to peer with +# - RPC port, to serve RPC of the op-node +# +# Other: +# - Sync mode: how to interact with the execution-engine, +# such that it enters the preferred form of syncing: +# - consensus-layer (block by block sync) +# - execution-layer (e.g. snap-sync) +# +# Tip: every CLI flag has an env-var equivalent (run `op-node --help` for more information) +./bin/op-node \ + --network=op-sepolia \ + --l1=ws://localhost:8546 \ + --l1.beacon=http://localhost:4000 \ + --l2=ws://localhost:9001 \ + --p2p.listen.tcp=9222 + --p2p.listen.udp=9222 + --rpc.port=7000 \ + --syncmode=execution-layer + +# If running inside docker, ake sure to mount the below persistent data as (host) volume, +# it may be lost on restart otherwise: +# - P2P private key: auto-generated when missing, used to maintain a stable peer identity. +# - Peerstore DB: remember peer records to connect with, used to not wait for peer discovery. +# - Discovery DB: maintain DHT data, to avoid repeating some discovery work after restarting. + --p2p.priv.path=opnode_p2p_priv.txt \ + --p2p.peerstore.path=opnode_peerstore_db \ + --p2p.discovery.path=opnode_discovery_db \ + --p2p.priv.path=opnode_p2p_priv.txt ``` -## Testing +## Usage -Run op-node unit tests: +### Build from source -```shell -make test +```bash +# from op-node dir: +make op-node +./bin/op-node --help ``` -## Running +### Run from source -Configuration options can be reviewed with: - -```shell -./bin/op-node --help +```bash +# from op-node dir: +go run ./cmd --help ``` -[eth-json-rpc-spec]: https://ethereum.github.io/execution-apis/api-documentation +### Build docker image -To start syncing the rollup: +See `op-node` docker-bake target. -Connect to one L1 Execution Client that supports the [Ethereum JSON-RPC spec][eth-json-rpc-spec], -an L1 Consensus Client that supports the [Beacon Node API](https://ethereum.github.io/beacon-APIs) and -an OP Stack based Execution Client that supports the [Ethereum JSON-RPC spec][eth-json-rpc-spec]: +## Implementation overview -- L1: use any L1 client, RPC, websocket, or IPC (connection config may differ) -- L2: use any OP Stack Execution Client like [`op-geth`](https://github.com/ethereum-optimism/op-geth) +### Interactions -Note that websockets or IPC is preferred for event notifications to improve sync, http RPC works with adaptive polling. + + -```shell -./bin/op-node \ - --l1=ws://localhost:8546 \ - --l1.beacon=http://localhost:4000 \ - --l2=ws://localhost:9001 \ - --rollup.config=./path-to-network-config/rollup.json \ - --rpc.addr=127.0.0.1 \ - --rpc.port=7000 -``` +## Product -## L2 Genesis Generation +The op-node **builds**, **relays** and **verifies** the canonical chain of blocks. -The `op-node` can generate geth compatible `genesis.json` files. These files -can be used with `geth init` to initialize the `StateDB` with accounts, storage, -code and balances. The L2 state must be initialized with predeploy contracts -that exist in the state and act as system level contracts. The `op-node` can -generate a genesis file with these predeploys configured correctly given -an L1 RPC URL, a deploy config, L2 genesis allocs and a L1 deployments artifact. +The op-node does not store critical data: +the op-node can recover from any existing L2 chain pre-state +that is sufficiently synced such that available input data can complete the sync. -The deploy config contains all of the config required to deploy the -system. Examples can be found in `packages/contracts-bedrock/deploy-config`. Each -deploy config file is a JSON file. The L2 allocs can be generated using a forge script -in the `contracts-bedrock` package and the L1 deployments are a JSON file that is the -output of doing a L1 contracts deployment. +The op-node **builds** blocks: +either from scratch as a sequencer, or from block-inputs (made available through L1) as verifier. -Example usage: +The block **relay** is a happy-path: the P2P sync is optional, and does not affect the ability to verify. +However, the block relay is still important for UX, as it lowers the latency to the latest state. -```bash -$ ./bin/op-node genesis l2 \ - --l1-rpc $ETH_RPC_URL \ - --deploy-config \ - --l2-allocs \ - --l1-deployments \ - --outfile.l2 \ - --outfile.rollup -``` +The blocks are **verified**: only valid L2 blocks that can be reproduced from L1 data are accepted. -## L1 Devnet Genesis Generation +### Optimization target -It is also possible to generate a devnet L1 `genesis.json` file. The L1 allocs can -be generated with the foundry L1 contracts deployment script if the extra parameter -`--sig 'runWithStateDump()` is added to the deployment command. + + +**Safely and reliably sync the canonical chain** + +The op-node implements the three core product features as following: + +- Block **building**: extend the chain at a throughput rate and latency that is safe to relay and verify. +- Block **relaying**: while keeping throughput high and latency low, prevent single points of failure. +- Block **verification**: efficiently sync, but always fully verify, follow the canonical chain. + +Trade-offs are made here: verification safety is at odds ideal throughput, latency, efficiency. +Or in other words: safety vs. liveness. Chain parameters determine this. +The implementation offers this trade-off, siding with safety by default, +and design-choices should aim to improve the trade-off. + +### Vision + +The op-node is changing in two ways: +- [Reliability](#reliability): improve the reliability with improved processing, testing and syncing. +- [Interoperability](#interoperability): cross-chain messaging support. + +#### Reliability + +- Parallel derivation processes: [Issue 10864](https://github.com/ethereum-optimism/optimism/issues/10864) +- Event tests: [Issue 13163](https://github.com/ethereum-optimism/optimism/issues/13163) +- Improving P2P sync: [Issue 11779](https://github.com/ethereum-optimism/optimism/issues/11779) + +#### Interoperability + +The OP Stack is make chains natively interoperable: +messages between chains form safety dependencies, and verified asynchronously. +Asynchronous verification entails that the op-node reorgs away a block +if and when the block is determined to be invalid. + +The [op-supervisor] specializes in this dependency verification work. + +The op-node encapsulates all the single-chain concerns: +it prepares the local safety data-points (DA confirmation and block contents) for the op-supervisor. + +The op-supervisor then verifies the cross-chain safety, and promotes the block safety level accordingly, +which the op-node then follows. + +See [Interop specs] and [Interop design-docs] for more information about interoperability. + +[op-supervisor]: ../op-supervisor/README.md + +### User stories + + + +As *a user* I want *reliability* so that I *don't miss blocks or fall out of sync*. +As *a RaaS dev* I want *easy configuration and monitoring* so that I *can run more chains*. +As *a customizoor* I want *clear extensible APIs* so that I *can avoid forking and be a contributor*. +As *a protocol dev* I want *integration with tests* so that I *assert protocol conformance* +As *a proof dev* I want *reusable state-transition code* so that I *don't reimplement the same thing*. + +## Design principles + + +- Encapsulate the state-transition: + - Use interfaces to abstract file-IO / concurrency / etc. away from state-transition logic. + - Ensure code-sharing with action-tests and op-program. +- No critical database: + - Persisting data is ok, but it should be recoverable from external data without too much work. + - The best chain "sync" is no sync. +- Keep the tech-stack compatible with ethereum L1: + - L1 offers well-adopted and battle tested libraries and standards, e.g. LibP2P, DiscV5, JSON-RPC. + - L1 supports a tech-stack in different languages, ensuring client-diversity, important to L2 as well. + - Downstream devs of OP-Stack should be able to pull in *one* instance of a library, that serves both OP-Stack and L1. + +## Failure modes + +This is a brief overview of what might fail, and how the op-node responds. + +### L1 downtime + +When the L1 data-source is temporarily unavailable the op-node `safe`/`finalized` progression halts. +Blocks may continue to sync through the happy-path if P2P connectivity is undisrupted. + +### No batch confirmation + +As per the [rollup-node spec] the sequencing-window ensures that after a bounded period of L1 blocks +the verifier will infer blocks, to ensure liveness of blocks with deposited transactions. +The op-node will continue to process the happy-path in the mean time, +which may have to be reorged out if it does not match the blocks that is inferred after sequencing window expiry. + +### L1 reorg + +L1 reorgs are detected passively during traversal: upon traversal to block `N+1`, +if the next canonical block has a parent-hash that does not match the +current block `N` we know the remote L1 chain view has diverged. + +When this happens, the op-node assumes the local view is wrong, and resets itself to follow that of the remote node, +dropping any non-canonical blocks in the process. + +### No L1 finality + +When L1 does not finalize for an extended period of time, +the op-node is also unable to finalize the L2 chain for the same time. + +Note that the `safe` block in the execution-layer is bootstrapped from the `finalized` block: +some verification work may repeat after a restart. + +Blocks will continue to be derived from L1 batch-submissions, and optimistic processing will also continue to function. + +### P2P failure + +On P2P failure, e.g. issues with peering or failed propagation of block-data, the `unsafe` part of the chain may stall. +The `unsafe` part of the chain will no longer progress optimistically ahead of the `safe` part. + +The `safe` blocks will continue to be derived from L1 however, providing a higher-latency access to the latest chain. + +The op-node may pick back up the latest `unsafe` blocks after recovering its P2P connectivity, +and buffering `unsafe` blocks until the `safe` blocks progress meets the first known buffered `unsafe` block. + +### Restarts and resyncing + +After a restart, or detection of missing chain data, +the op-node dynamically determines what L1 data is required to continue, based on the syncing state of execution-engine. +If the sync-state is far behind, the op-node may need archived blob data to sync from the original L1 inputs. + +A faster alternative may be to bootstrap through the execution-layer sync mode, +where the execution-engine may perform an optimized long-range sync, such as snap-sync. + +## Testing + + + +- Unit tests: encapsulated functionality, fuzz tests, etc. in the op-node Go packages. +- `op-e2e` action tests: in-progress Go testing, focused on the onchain aspects, + e.g. state-transition edge-cases. This applies primarily to the derivation pipeline. +- `op-e2e` system tests: in-process Go testing, focused on the offchain aspects of the op-node, + e.g. background work, P2P integration, general service functionality. +- Local devnet tests: full end to end testing, but set up on minimal resources. +- Kurtosis tests: new automated devnet-like testing. Work in progress. +- Long-running devnet: roll-out for experimental features, to ensure sufficient stability for testnet users. +- Long-running testnet: battle-testing in public environment. +- Shadow-forks: design phase, testing experiments against shadow copies of real networks. -```bash -$ ./bin/op-node genesis l1 \ - --deploy-config $CONTRACTS_BEDROCK/deploy-config \ - --l1-deployments \ - --l1-allocs -``` diff --git a/op-node/bindings/l1block.go b/op-node/bindings/l1block.go index 41a24ec388781..5793ce341edb0 100644 --- a/op-node/bindings/l1block.go +++ b/op-node/bindings/l1block.go @@ -30,8 +30,8 @@ var ( // L1BlockMetaData contains all meta data concerning the L1Block contract. var L1BlockMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"function\",\"name\":\"DEPOSITOR_ACCOUNT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"baseFeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"basefee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batcherHash\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobBaseFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobBaseFeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"hash\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1FeeOverhead\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1FeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"number\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"sequenceNumber\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setL1BlockValues\",\"inputs\":[{\"name\":\"_number\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_timestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_basefee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_hash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_sequenceNumber\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_batcherHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_l1FeeOverhead\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_l1FeeScalar\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setL1BlockValuesEcotone\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"timestamp\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"}]", - Bin: "0x608060405234801561001057600080fd5b5061053e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80638381f58a11610097578063c598591811610066578063c598591814610229578063e591b28214610249578063e81b2c6d14610289578063f82061401461029257600080fd5b80638381f58a146101e35780638b239f73146101f75780639e8c496614610200578063b80777ea1461020957600080fd5b806354fd4d50116100d357806354fd4d50146101335780635cf249691461017c57806364ca23ef1461018557806368d5dca6146101b257600080fd5b8063015d8eb9146100fa57806309bd5a601461010f578063440a5e201461012b575b600080fd5b61010d61010836600461044c565b61029b565b005b61011860025481565b6040519081526020015b60405180910390f35b61010d6103da565b61016f6040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b60405161012291906104be565b61011860015481565b6003546101999067ffffffffffffffff1681565b60405167ffffffffffffffff9091168152602001610122565b6003546101ce9068010000000000000000900463ffffffff1681565b60405163ffffffff9091168152602001610122565b6000546101999067ffffffffffffffff1681565b61011860055481565b61011860065481565b6000546101999068010000000000000000900467ffffffffffffffff1681565b6003546101ce906c01000000000000000000000000900463ffffffff1681565b61026473deaddeaddeaddeaddeaddeaddeaddeaddead000181565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610122565b61011860045481565b61011860075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610342576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461040357633cc50b456000526004601cfd5b60043560801c60035560143560801c600055602435600155604435600755606435600255608435600455565b803567ffffffffffffffff8116811461044757600080fd5b919050565b600080600080600080600080610100898b03121561046957600080fd5b6104728961042f565b975061048060208a0161042f565b9650604089013595506060890135945061049c60808a0161042f565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b818110156104eb578581018301518582016040015282016104cf565b818111156104fd576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a", + ABI: "[{\"type\":\"function\",\"name\":\"DEPOSITOR_ACCOUNT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"HISTORY_SIZE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"baseFeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"basefee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batcherHash\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobBaseFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobBaseFeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blockHash\",\"inputs\":[{\"name\":\"_historyNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"hash\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"historyHashes\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1FeeOverhead\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1FeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"number\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"sequenceNumber\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setL1BlockValues\",\"inputs\":[{\"name\":\"_number\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_timestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_basefee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_hash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_sequenceNumber\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_batcherHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_l1FeeOverhead\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_l1FeeScalar\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setL1BlockValuesEcotone\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"timestamp\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"}]", + Bin: "0x608060405234801561001057600080fd5b5061075e806100206000396000f3fe608060405234801561001057600080fd5b50600436106101365760003560e01c806385df51fd116100b2578063b80777ea11610081578063e591b28211610066578063e591b282146102b9578063e81b2c6d146102f9578063f82061401461030257600080fd5b8063b80777ea14610279578063c59859181461029957600080fd5b806385df51fd146102415780638b239f731461025457806392abaa421461025d5780639e8c49661461027057600080fd5b80635cf2496911610109578063652c462e116100ee578063652c462e146101f357806368d5dca6146101fc5780638381f58a1461022d57600080fd5b80635cf24969146101bd57806364ca23ef146101c657600080fd5b8063015d8eb91461013b57806309bd5a6014610150578063440a5e201461016c57806354fd4d5014610174575b600080fd5b61014e61014936600461058b565b61030b565b005b61015960025481565b6040519081526020015b60405180910390f35b61014e61044a565b6101b06040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b60405161016391906105fd565b61015960015481565b6003546101da9067ffffffffffffffff1681565b60405167ffffffffffffffff9091168152602001610163565b61015961200081565b6003546102189068010000000000000000900463ffffffff1681565b60405163ffffffff9091168152602001610163565b6000546101da9067ffffffffffffffff1681565b61015961024f366004610670565b6104d2565b61015960055481565b61015961026b366004610670565b610556565b61015960065481565b6000546101da9068010000000000000000900467ffffffffffffffff1681565b600354610218906c01000000000000000000000000900463ffffffff1681565b6102d473deaddeaddeaddeaddeaddeaddeaddeaddead000181565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610163565b61015960045481565b61015960075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146103b2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461047357633cc50b456000526004601cfd5b60048035608090811c600355601435901c600081905560243560015560443560075560643560028190556084359092556008906104bd906120009067ffffffffffffffff16610689565b61200081106104ce576104ce6106c4565b0155565b60008054819067ffffffffffffffff166120008110156104f5576000915061050f565b61050161200082610722565b61050c906001610739565b91505b81841015801561051e57508084105b1561054c57600861053161200086610689565b6120008110610542576105426106c4565b0154949350505050565b5060009392505050565b600881612000811061056757600080fd5b0154905081565b803567ffffffffffffffff8116811461058657600080fd5b919050565b600080600080600080600080610100898b0312156105a857600080fd5b6105b18961056e565b97506105bf60208a0161056e565b965060408901359550606089013594506105db60808a0161056e565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b8181101561062a5785810183015185820160400152820161060e565b8181111561063c576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561068257600080fd5b5035919050565b6000826106bf577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500690565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015610734576107346106f3565b500390565b6000821982111561074c5761074c6106f3565b50019056fea164736f6c634300080f000a", } // L1BlockABI is the input ABI used to generate the binding from. @@ -232,6 +232,37 @@ func (_L1Block *L1BlockCallerSession) DEPOSITORACCOUNT() (common.Address, error) return _L1Block.Contract.DEPOSITORACCOUNT(&_L1Block.CallOpts) } +// HISTORYSIZE is a free data retrieval call binding the contract method 0x652c462e. +// +// Solidity: function HISTORY_SIZE() view returns(uint256) +func (_L1Block *L1BlockCaller) HISTORYSIZE(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _L1Block.contract.Call(opts, &out, "HISTORY_SIZE") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// HISTORYSIZE is a free data retrieval call binding the contract method 0x652c462e. +// +// Solidity: function HISTORY_SIZE() view returns(uint256) +func (_L1Block *L1BlockSession) HISTORYSIZE() (*big.Int, error) { + return _L1Block.Contract.HISTORYSIZE(&_L1Block.CallOpts) +} + +// HISTORYSIZE is a free data retrieval call binding the contract method 0x652c462e. +// +// Solidity: function HISTORY_SIZE() view returns(uint256) +func (_L1Block *L1BlockCallerSession) HISTORYSIZE() (*big.Int, error) { + return _L1Block.Contract.HISTORYSIZE(&_L1Block.CallOpts) +} + // BaseFeeScalar is a free data retrieval call binding the contract method 0xc5985918. // // Solidity: function baseFeeScalar() view returns(uint32) @@ -387,6 +418,37 @@ func (_L1Block *L1BlockCallerSession) BlobBaseFeeScalar() (uint32, error) { return _L1Block.Contract.BlobBaseFeeScalar(&_L1Block.CallOpts) } +// BlockHash is a free data retrieval call binding the contract method 0x85df51fd. +// +// Solidity: function blockHash(uint256 _historyNumber) view returns(bytes32) +func (_L1Block *L1BlockCaller) BlockHash(opts *bind.CallOpts, _historyNumber *big.Int) ([32]byte, error) { + var out []interface{} + err := _L1Block.contract.Call(opts, &out, "blockHash", _historyNumber) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// BlockHash is a free data retrieval call binding the contract method 0x85df51fd. +// +// Solidity: function blockHash(uint256 _historyNumber) view returns(bytes32) +func (_L1Block *L1BlockSession) BlockHash(_historyNumber *big.Int) ([32]byte, error) { + return _L1Block.Contract.BlockHash(&_L1Block.CallOpts, _historyNumber) +} + +// BlockHash is a free data retrieval call binding the contract method 0x85df51fd. +// +// Solidity: function blockHash(uint256 _historyNumber) view returns(bytes32) +func (_L1Block *L1BlockCallerSession) BlockHash(_historyNumber *big.Int) ([32]byte, error) { + return _L1Block.Contract.BlockHash(&_L1Block.CallOpts, _historyNumber) +} + // Hash is a free data retrieval call binding the contract method 0x09bd5a60. // // Solidity: function hash() view returns(bytes32) @@ -418,6 +480,37 @@ func (_L1Block *L1BlockCallerSession) Hash() ([32]byte, error) { return _L1Block.Contract.Hash(&_L1Block.CallOpts) } +// HistoryHashes is a free data retrieval call binding the contract method 0x92abaa42. +// +// Solidity: function historyHashes(uint256 ) view returns(bytes32) +func (_L1Block *L1BlockCaller) HistoryHashes(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) { + var out []interface{} + err := _L1Block.contract.Call(opts, &out, "historyHashes", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// HistoryHashes is a free data retrieval call binding the contract method 0x92abaa42. +// +// Solidity: function historyHashes(uint256 ) view returns(bytes32) +func (_L1Block *L1BlockSession) HistoryHashes(arg0 *big.Int) ([32]byte, error) { + return _L1Block.Contract.HistoryHashes(&_L1Block.CallOpts, arg0) +} + +// HistoryHashes is a free data retrieval call binding the contract method 0x92abaa42. +// +// Solidity: function historyHashes(uint256 ) view returns(bytes32) +func (_L1Block *L1BlockCallerSession) HistoryHashes(arg0 *big.Int) ([32]byte, error) { + return _L1Block.Contract.HistoryHashes(&_L1Block.CallOpts, arg0) +} + // L1FeeOverhead is a free data retrieval call binding the contract method 0x8b239f73. // // Solidity: function l1FeeOverhead() view returns(uint256) diff --git a/op-node/chaincfg/chains.go b/op-node/chaincfg/chains.go index b16e9b802ae74..01c6b2428fbbe 100644 --- a/op-node/chaincfg/chains.go +++ b/op-node/chaincfg/chains.go @@ -2,6 +2,7 @@ package chaincfg import ( "fmt" + "sort" "strings" "github.com/ethereum-optimism/superchain-registry/superchain" @@ -36,6 +37,7 @@ func AvailableNetworks() []string { for _, cfg := range superchain.OPChains { networks = append(networks, cfg.Chain+"-"+cfg.Superchain) } + sort.Strings(networks) return networks } diff --git a/op-node/chaincfg/chains_test.go b/op-node/chaincfg/chains_test.go index 15f062c926071..bd9956253cd57 100644 --- a/op-node/chaincfg/chains_test.go +++ b/op-node/chaincfg/chains_test.go @@ -52,21 +52,22 @@ var mainnetCfg = rollup.Config{ GasLimit: 30_000_000, }, }, - BlockTime: 2, - MaxSequencerDrift: 600, - SeqWindowSize: 3600, - ChannelTimeoutBedrock: 300, - L1ChainID: big.NewInt(1), - L2ChainID: big.NewInt(10), - BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"), - DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"), - L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"), - RegolithTime: u64Ptr(0), - CanyonTime: u64Ptr(1704992401), - DeltaTime: u64Ptr(1708560000), - EcotoneTime: u64Ptr(1710374401), - FjordTime: u64Ptr(1720627201), - GraniteTime: u64Ptr(1726070401), + BlockTime: 2, + MaxSequencerDrift: 600, + SeqWindowSize: 3600, + ChannelTimeoutBedrock: 300, + L1ChainID: big.NewInt(1), + L2ChainID: big.NewInt(10), + BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"), + DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"), + L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"), + RegolithTime: u64Ptr(0), + CanyonTime: u64Ptr(1704992401), + DeltaTime: u64Ptr(1708560000), + EcotoneTime: u64Ptr(1710374401), + FjordTime: u64Ptr(1720627201), + GraniteTime: u64Ptr(1726070401), + // HoloceneTime: TBD ProtocolVersionsAddress: common.HexToAddress("0x8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935"), } @@ -103,6 +104,7 @@ var sepoliaCfg = rollup.Config{ EcotoneTime: u64Ptr(1708534800), FjordTime: u64Ptr(1716998400), GraniteTime: u64Ptr(1723478400), + HoloceneTime: u64Ptr(1732633200), ProtocolVersionsAddress: common.HexToAddress("0x79ADD5713B383DAa0a138d3C4780C7A1804a8090"), } @@ -139,6 +141,7 @@ var sepoliaDev0Cfg = rollup.Config{ EcotoneTime: u64Ptr(1706634000), FjordTime: u64Ptr(1715961600), GraniteTime: u64Ptr(1723046400), + HoloceneTime: u64Ptr(1731682800), ProtocolVersionsAddress: common.HexToAddress("0x252CbE9517F731C618961D890D534183822dcC8d"), } diff --git a/op-node/cmd/batch_decoder/reassemble/reassemble.go b/op-node/cmd/batch_decoder/reassemble/reassemble.go index d8fc136b50438..08c32bf714478 100644 --- a/op-node/cmd/batch_decoder/reassemble/reassemble.go +++ b/op-node/cmd/batch_decoder/reassemble/reassemble.go @@ -94,7 +94,7 @@ func writeChannel(ch ChannelWithMetadata, filename string) error { // from the channel. Returns a ChannelWithMetadata struct containing all the relevant data. func ProcessFrames(cfg Config, rollupCfg *rollup.Config, id derive.ChannelID, frames []FrameWithMetadata) ChannelWithMetadata { spec := rollup.NewChainSpec(rollupCfg) - ch := derive.NewChannel(id, eth.L1BlockRef{Number: frames[0].InclusionBlock}) + ch := derive.NewChannel(id, eth.L1BlockRef{Number: frames[0].InclusionBlock}, rollupCfg.IsHolocene(frames[0].Timestamp)) invalidFrame := false for _, frame := range frames { diff --git a/op-node/cmd/interop/interop.go b/op-node/cmd/interop/interop.go new file mode 100644 index 0000000000000..3e6f75d530bb0 --- /dev/null +++ b/op-node/cmd/interop/interop.go @@ -0,0 +1,299 @@ +package interop + +import ( + "fmt" + "math/big" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/urfave/cli/v2" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/interopgen" + op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/ioutil" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/crypto" +) + +var EnvPrefix = "OP_INTEROP" + +var ( + l1ChainIDFlag = &cli.Uint64Flag{ + Name: "l1.chainid", + Value: 900100, + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "L1_CHAINID"), + } + l2ChainIDsFlag = &cli.Uint64SliceFlag{ + Name: "l2.chainids", + Value: cli.NewUint64Slice(900200, 900201), + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "L2_CHAINIDS"), + } + timestampFlag = &cli.Uint64Flag{ + Name: "timestamp", + Value: 0, + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "TIMESTAMP"), + Usage: "Will use current timestamp, plus 5 seconds, if not set", + } + artifactsDirFlag = &cli.StringFlag{ + Name: "artifacts-dir", + Value: "packages/contracts-bedrock/forge-artifacts", + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "ARTIFACTS_DIR"), + } + foundryDirFlag = &cli.StringFlag{ + Name: "foundry-dir", + Value: "packages/contracts-bedrock", + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "FOUNDRY_DIR"), + Usage: "Optional, for source-map info during genesis generation", + } + outDirFlag = &cli.StringFlag{ + Name: "out-dir", + Value: ".interop-devnet", + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "OUT_DIR"), + } + // used in both dev-setup and devkey commands + mnemonicFlag = &cli.StringFlag{ + Name: "mnemonic", + Value: devkeys.TestMnemonic, + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "MNEMONIC"), + } + // for devkey command + devkeyDomainFlag = &cli.StringFlag{ + Name: "domain", + Value: "chain-operator", + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "DEVKEY_DOMAIN"), + } + devkeyChainIdFlag = &cli.Uint64Flag{ + Name: "chainid", + Value: 0, + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "DEVKEY_CHAINID"), + } + devkeyNameFlag = &cli.StringFlag{ + Name: "name", + EnvVars: op_service.PrefixEnvVar(EnvPrefix, "DEVKEY_NAME"), + } +) + +var InteropDevSetup = &cli.Command{ + Name: "dev-setup", + Usage: "Generate devnet genesis configs with one L1 and multiple L2s", + Flags: cliapp.ProtectFlags(append([]cli.Flag{ + l1ChainIDFlag, + l2ChainIDsFlag, + timestampFlag, + mnemonicFlag, + artifactsDirFlag, + foundryDirFlag, + outDirFlag, + }, oplog.CLIFlags(EnvPrefix)...)), + Action: func(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + logger := oplog.NewLogger(cliCtx.App.Writer, logCfg) + + recipe := &interopgen.InteropDevRecipe{ + L1ChainID: cliCtx.Uint64(l1ChainIDFlag.Name), + L2ChainIDs: cliCtx.Uint64Slice(l2ChainIDsFlag.Name), + GenesisTimestamp: cliCtx.Uint64(timestampFlag.Name), + } + if recipe.GenesisTimestamp == 0 { + recipe.GenesisTimestamp = uint64(time.Now().Unix() + 5) + } + mnemonic := strings.TrimSpace(cliCtx.String(mnemonicFlag.Name)) + if mnemonic == devkeys.TestMnemonic { + logger.Warn("Using default test mnemonic!") + } + keys, err := devkeys.NewMnemonicDevKeys(mnemonic) + if err != nil { + return fmt.Errorf("failed to setup dev keys from mnemonic: %w", err) + } + worldCfg, err := recipe.Build(keys) + if err != nil { + return fmt.Errorf("failed to build deploy configs from interop recipe: %w", err) + } + if err := worldCfg.Check(logger); err != nil { + return fmt.Errorf("invalid deploy configs: %w", err) + } + artifactsDir := cliCtx.String(artifactsDirFlag.Name) + af := foundry.OpenArtifactsDir(artifactsDir) + var srcFs *foundry.SourceMapFS + if cliCtx.IsSet(foundryDirFlag.Name) { + srcDir := cliCtx.String(foundryDirFlag.Name) + srcFs = foundry.NewSourceMapFS(os.DirFS(srcDir)) + } + worldDeployment, worldOutput, err := interopgen.Deploy(logger, af, srcFs, worldCfg) + if err != nil { + return fmt.Errorf("failed to deploy interop dev setup: %w", err) + } + outDir := cliCtx.String(outDirFlag.Name) + // Write deployments + { + deploymentsDir := filepath.Join(outDir, "deployments") + l1Dir := filepath.Join(deploymentsDir, "l1") + if err := writeJson(filepath.Join(l1Dir, "common.json"), worldDeployment.L1); err != nil { + return fmt.Errorf("failed to write L1 deployment data: %w", err) + } + if err := writeJson(filepath.Join(l1Dir, "superchain.json"), worldDeployment.Superchain); err != nil { + return fmt.Errorf("failed to write Superchain deployment data: %w", err) + } + l2sDir := filepath.Join(deploymentsDir, "l2") + for id, dep := range worldDeployment.L2s { + l2Dir := filepath.Join(l2sDir, id) + if err := writeJson(filepath.Join(l2Dir, "addresses.json"), dep); err != nil { + return fmt.Errorf("failed to write L2 %s deployment data: %w", id, err) + } + } + } + // write genesis + { + genesisDir := filepath.Join(outDir, "genesis") + l1Dir := filepath.Join(genesisDir, "l1") + if err := writeJson(filepath.Join(l1Dir, "genesis.json"), worldOutput.L1.Genesis); err != nil { + return fmt.Errorf("failed to write L1 genesis data: %w", err) + } + l2sDir := filepath.Join(genesisDir, "l2") + for id, dep := range worldOutput.L2s { + l2Dir := filepath.Join(l2sDir, id) + if err := writeJson(filepath.Join(l2Dir, "genesis.json"), dep.Genesis); err != nil { + return fmt.Errorf("failed to write L2 %s genesis config: %w", id, err) + } + if err := writeJson(filepath.Join(l2Dir, "rollup.json"), dep.RollupCfg); err != nil { + return fmt.Errorf("failed to write L2 %s rollup config: %w", id, err) + } + } + } + return nil + }, +} + +func writeJson(path string, content any) error { + return jsonutil.WriteJSON[any](content, ioutil.ToBasicFile(path, 0o755)) +} + +var DevKeySecretCmd = &cli.Command{ + Name: "secret", + Usage: "Retrieve devkey secret, by specifying domain, chain ID, name.", + Flags: cliapp.ProtectFlags([]cli.Flag{ + mnemonicFlag, + devkeyDomainFlag, + devkeyChainIdFlag, + devkeyNameFlag, + }), + Action: func(context *cli.Context) error { + mnemonic := context.String(mnemonicFlag.Name) + domain := context.String(devkeyDomainFlag.Name) + chainID := context.Uint64(devkeyChainIdFlag.Name) + chainIDBig := new(big.Int).SetUint64(chainID) + name := context.String(devkeyNameFlag.Name) + k, err := parseKey(domain, chainIDBig, name) + if err != nil { + return err + } + mnemonicKeys, err := devkeys.NewMnemonicDevKeys(mnemonic) + if err != nil { + return err + } + secret, err := mnemonicKeys.Secret(k) + if err != nil { + return err + } + secretBin := crypto.FromECDSA(secret) + _, err = fmt.Fprintf(context.App.Writer, "%x", secretBin) + if err != nil { + return fmt.Errorf("failed to output secret key: %w", err) + } + return nil + }, +} + +var DevKeyAddressCmd = &cli.Command{ + Name: "address", + Usage: "Retrieve devkey address, by specifying domain, chain ID, name.", + Flags: cliapp.ProtectFlags([]cli.Flag{ + mnemonicFlag, + devkeyDomainFlag, + devkeyChainIdFlag, + devkeyNameFlag, + }), + Action: func(context *cli.Context) error { + mnemonic := context.String(mnemonicFlag.Name) + domain := context.String(devkeyDomainFlag.Name) + chainID := context.Uint64(devkeyChainIdFlag.Name) + chainIDBig := new(big.Int).SetUint64(chainID) + name := context.String(devkeyNameFlag.Name) + k, err := parseKey(domain, chainIDBig, name) + if err != nil { + return err + } + mnemonicKeys, err := devkeys.NewMnemonicDevKeys(mnemonic) + if err != nil { + return err + } + addr, err := mnemonicKeys.Address(k) + if err != nil { + return err + } + _, err = fmt.Fprintf(context.App.Writer, "%s", addr) + if err != nil { + return fmt.Errorf("failed to output address: %w", err) + } + return nil + }, +} + +var DevKeyCmd = &cli.Command{ + Name: "devkey", + Usage: "Retrieve devkey secret or address", + Subcommands: cli.Commands{ + DevKeySecretCmd, + DevKeyAddressCmd, + }, +} + +func parseKey(domain string, chainID *big.Int, name string) (devkeys.Key, error) { + switch domain { + case "user": + index, err := strconv.ParseUint(name, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse user index: %w", err) + } + return devkeys.ChainUserKey{ + ChainID: chainID, + Index: index, + }, nil + case "chain-operator": + var role devkeys.ChainOperatorRole + if err := role.UnmarshalText([]byte(name)); err != nil { + return nil, fmt.Errorf("failed to parse chain operator role: %w", err) + } + return devkeys.ChainOperatorKey{ + ChainID: chainID, + Role: role, + }, nil + case "superchain-operator": + var role devkeys.SuperchainOperatorRole + if err := role.UnmarshalText([]byte(name)); err != nil { + return nil, fmt.Errorf("failed to parse chain operator role: %w", err) + } + return devkeys.SuperchainOperatorKey{ + ChainID: chainID, + Role: role, + }, nil + default: + return nil, fmt.Errorf("unknown devkey domain %q", domain) + } +} + +var InteropCmd = &cli.Command{ + Name: "interop", + Usage: "Experimental tools for OP-Stack interop networks.", + Subcommands: cli.Commands{ + InteropDevSetup, + DevKeyCmd, + }, +} diff --git a/op-node/cmd/main.go b/op-node/cmd/main.go index 8f6688b51cbf0..b82b3f6babce2 100644 --- a/op-node/cmd/main.go +++ b/op-node/cmd/main.go @@ -12,6 +12,7 @@ import ( opnode "github.com/ethereum-optimism/optimism/op-node" "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/cmd/genesis" + "github.com/ethereum-optimism/optimism/op-node/cmd/interop" "github.com/ethereum-optimism/optimism/op-node/cmd/networks" "github.com/ethereum-optimism/optimism/op-node/cmd/p2p" "github.com/ethereum-optimism/optimism/op-node/flags" @@ -62,6 +63,7 @@ func main() { Name: "networks", Subcommands: networks.Subcommands, }, + interop.InteropCmd, } ctx := ctxinterrupt.WithSignalWaiterMain(context.Background()) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 54334c150296a..998d1b69fe1be 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -73,6 +73,7 @@ var ( EnvVars: prefixEnvVars("L1_BEACON"), Category: RollupCategory, } + /* Optional Flags */ SupervisorAddr = &cli.StringFlag{ Name: "supervisor", Usage: "RPC address of interop supervisor service for cross-chain safety verification." + @@ -80,7 +81,6 @@ var ( Hidden: true, // hidden for now during early testing. EnvVars: prefixEnvVars("SUPERVISOR"), } - /* Optional Flags */ BeaconHeader = &cli.StringFlag{ Name: "l1.beacon-header", Usage: "Optional HTTP header to add to all requests to the L1 Beacon endpoint. Format: 'X-Key: Value'", @@ -372,6 +372,12 @@ var ( Value: time.Second * 1, Category: SequencerCategory, } + DACUrlsFlag = &cli.StringFlag{ + Name: "dac.urls", + Usage: "dac urls for sequencer when l2 blob is enabled", + EnvVars: prefixEnvVars("DAC_URLS"), + Category: SequencerCategory, + } ) var requiredFlags = []cli.Flag{ @@ -419,6 +425,7 @@ var optionalFlags = []cli.Flag{ ConductorRpcTimeoutFlag, SafeDBPath, L2EngineKind, + DACUrlsFlag, } var DeprecatedFlags = []cli.Flag{ diff --git a/op-node/flags/p2p_flags.go b/op-node/flags/p2p_flags.go index 269b973c52da6..6a38fbb817f89 100644 --- a/op-node/flags/p2p_flags.go +++ b/op-node/flags/p2p_flags.go @@ -7,6 +7,7 @@ import ( "github.com/urfave/cli/v2" "github.com/ethereum-optimism/optimism/op-node/p2p" + opsigner "github.com/ethereum-optimism/optimism/op-service/signer" ) func p2pEnv(envprefix, v string) []string { @@ -87,7 +88,7 @@ func deprecatedP2PFlags(envPrefix string) []cli.Flag { // None of these flags are strictly required. // Some are hidden if they are too technical, or not recommended. func P2PFlags(envPrefix string) []cli.Flag { - return []cli.Flag{ + return append([]cli.Flag{ &cli.BoolFlag{ Name: DisableP2PName, Usage: "Completely disable the P2P stack", @@ -410,5 +411,5 @@ func P2PFlags(envPrefix string) []cli.Flag { Required: false, EnvVars: p2pEnv(envPrefix, "PING"), }, - } + }, opsigner.CLIFlags(envPrefix, P2PCategory)...) } diff --git a/op-node/justfile b/op-node/justfile new file mode 100644 index 0000000000000..46aadcc84b391 --- /dev/null +++ b/op-node/justfile @@ -0,0 +1,49 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X github.com/ethereum-optimism/optimism/op-node/version.Version=" + VERSION + " " + \ + "-X github.com/ethereum-optimism/optimism/op-node/version.Meta=" + VERSION_META + " " + \ + "") + "'" + +BINARY := "./bin/op-node" + +# Build op-node binary +op-node: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") + +# Generate mocks +generate-mocks: (go_generate "./...") + +# Update readme +readme: + doctoc README.md + +[private] +node_fuzz_task FUZZ TIME='10s': (go_fuzz FUZZ TIME "./rollup/derive") + +# Run fuzz tests +fuzz: + printf "%s\n" \ + "FuzzL1InfoBedrockRoundTrip" \ + "FuzzL1InfoEcotoneRoundTrip" \ + "FuzzL1InfoAgainstContract" \ + "FuzzUnmarshallLogEvent" \ + "FuzzParseFrames" \ + "FuzzFrameUnmarshalBinary" \ + "FuzzBatchRoundTrip" \ + "FuzzDeriveDepositsRoundTrip" \ + "FuzzDeriveDepositsBadVersion" \ + "FuzzParseL1InfoDepositTxDataValid" \ + "FuzzParseL1InfoDepositTxDataBadLength" \ + "FuzzRejectCreateBlockBadTimestamp" \ + "FuzzDecodeDepositTxDataToL1Info" \ + | parallel -j {{PARALLEL_JOBS}} {{just_executable()}} node_fuzz_task {} diff --git a/op-node/metrics/metrics.go b/op-node/metrics/metrics.go index 88d8c4d0caa36..6e1b664eca2b6 100644 --- a/op-node/metrics/metrics.go +++ b/op-node/metrics/metrics.go @@ -35,6 +35,7 @@ type Metricer interface { RecordRPCClientRequest(method string) func(err error) RecordRPCClientResponse(method string, err error) SetDerivationIdle(status bool) + SetSequencerState(active bool) RecordPipelineReset() RecordSequencingError() RecordPublishingError() @@ -48,7 +49,7 @@ type Metricer interface { RecordL2Ref(name string, ref eth.L2BlockRef) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) RecordDerivedBatches(batchType string) - CountSequencedTxs(count int) + CountSequencedTxsInBlock(txns int, deposits int) RecordL1ReorgDepth(d uint64) RecordSequencerInconsistentL1Origin(from eth.BlockID, to eth.BlockID) RecordSequencerReset() @@ -94,6 +95,7 @@ type Metrics struct { DerivationErrors *metrics.Event SequencingErrors *metrics.Event PublishingErrors *metrics.Event + SequencerActive prometheus.Gauge EmittedEvents *prometheus.CounterVec ProcessedEvents *prometheus.CounterVec @@ -133,7 +135,7 @@ type Metrics struct { L1ReorgDepth prometheus.Histogram - TransactionsSequencedTotal prometheus.Counter + TransactionsSequencedTotal *prometheus.CounterVec AltDAMetrics altda.Metricer @@ -209,6 +211,11 @@ func NewMetrics(procName string) *Metrics { DerivationErrors: metrics.NewEvent(factory, ns, "", "derivation_errors", "derivation errors"), SequencingErrors: metrics.NewEvent(factory, ns, "", "sequencing_errors", "sequencing errors"), PublishingErrors: metrics.NewEvent(factory, ns, "", "publishing_errors", "p2p publishing errors"), + SequencerActive: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "sequencer_active", + Help: "1 if sequencer active, 0 otherwise", + }), EmittedEvents: factory.NewCounterVec( prometheus.CounterOpts{ @@ -261,12 +268,11 @@ func NewMetrics(procName string) *Metrics { Help: "Histogram of L1 Reorg Depths", }), - TransactionsSequencedTotal: factory.NewGauge(prometheus.GaugeOpts{ + TransactionsSequencedTotal: factory.NewCounterVec(prometheus.CounterOpts{ Namespace: ns, Name: "transactions_sequenced_total", Help: "Count of total transactions sequenced", - }), - + }, []string{"type"}), PeerCount: factory.NewGauge(prometheus.GaugeOpts{ Namespace: ns, Subsystem: "p2p", @@ -470,6 +476,14 @@ func (m *Metrics) SetDerivationIdle(status bool) { m.DerivationIdle.Set(val) } +func (m *Metrics) SetSequencerState(active bool) { + var val float64 + if active { + val = 1 + } + m.SequencerActive.Set(val) +} + func (m *Metrics) RecordPipelineReset() { m.PipelineResets.Record() } @@ -516,8 +530,9 @@ func (m *Metrics) RecordDerivedBatches(batchType string) { m.DerivedBatches.Record(batchType) } -func (m *Metrics) CountSequencedTxs(count int) { - m.TransactionsSequencedTotal.Add(float64(count)) +func (m *Metrics) CountSequencedTxsInBlock(txns int, deposits int) { + m.TransactionsSequencedTotal.WithLabelValues("deposits").Add(float64(deposits)) + m.TransactionsSequencedTotal.WithLabelValues("txns").Add(float64(txns - deposits)) } func (m *Metrics) RecordL1ReorgDepth(d uint64) { @@ -686,6 +701,9 @@ func (n *noopMetricer) RecordUp() { func (n *noopMetricer) SetDerivationIdle(status bool) { } +func (m *noopMetricer) SetSequencerState(active bool) { +} + func (n *noopMetricer) RecordPipelineReset() { } @@ -725,7 +743,7 @@ func (n *noopMetricer) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, func (n *noopMetricer) RecordDerivedBatches(batchType string) { } -func (n *noopMetricer) CountSequencedTxs(count int) { +func (n *noopMetricer) CountSequencedTxsInBlock(txns int, deposits int) { } func (n *noopMetricer) RecordL1ReorgDepth(d uint64) { diff --git a/op-node/node/client.go b/op-node/node/client.go index a561a7678d24d..796375a843653 100644 --- a/op-node/node/client.go +++ b/op-node/node/client.go @@ -66,7 +66,7 @@ func (cfg *L2EndpointConfig) Setup(ctx context.Context, log log.Logger, rollupCf auth := rpc.WithHTTPAuth(gn.NewJWTAuth(cfg.L2EngineJWTSecret)) opts := []client.RPCOption{ client.WithGethRPCOptions(auth), - client.WithDialBackoff(10), + client.WithDialAttempts(10), } l2Node, err := client.NewRPC(ctx, log, cfg.L2EngineAddr, opts...) if err != nil { @@ -140,7 +140,7 @@ func (cfg *L1EndpointConfig) Check() error { func (cfg *L1EndpointConfig) Setup(ctx context.Context, log log.Logger, rollupCfg *rollup.Config) (client.RPC, *sources.L1ClientConfig, error) { opts := []client.RPCOption{ client.WithHttpPollInterval(cfg.HttpPollInterval), - client.WithDialBackoff(10), + client.WithDialAttempts(10), } if cfg.RateLimit != 0 { opts = append(opts, client.WithRateLimit(cfg.RateLimit, cfg.BatchSize)) diff --git a/op-node/node/conductor.go b/op-node/node/conductor.go index ff5723889b95d..cc45f4bfabc46 100644 --- a/op-node/node/conductor.go +++ b/op-node/node/conductor.go @@ -13,15 +13,17 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/conductor" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/locks" "github.com/ethereum-optimism/optimism/op-service/retry" ) // ConductorClient is a client for the op-conductor RPC service. type ConductorClient struct { - cfg *Config - metrics *metrics.Metrics - log log.Logger - apiClient *conductorRpc.APIClient + cfg *Config + metrics *metrics.Metrics + log log.Logger + + apiClient locks.RWValue[*conductorRpc.APIClient] // overrideLeader is used to override the leader check for disaster recovery purposes. // During disaster situations where the cluster is unhealthy (no leader, only 1 or less nodes up), @@ -41,15 +43,23 @@ func NewConductorClient(cfg *Config, log log.Logger, metrics *metrics.Metrics) c } // Initialize initializes the conductor client. -func (c *ConductorClient) initialize() error { - if c.apiClient != nil { +func (c *ConductorClient) initialize(ctx context.Context) error { + c.apiClient.Lock() + defer c.apiClient.Unlock() + if c.apiClient.Value != nil { return nil } - conductorRpcClient, err := dial.DialRPCClientWithTimeout(context.Background(), time.Minute*1, c.log, c.cfg.ConductorRpc) + endpoint, err := retry.Do[string](ctx, 10, retry.Exponential(), func() (string, error) { + return c.cfg.ConductorRpc(ctx) + }) + if err != nil { + return fmt.Errorf("no conductor RPC endpoint available: %w", err) + } + conductorRpcClient, err := dial.DialRPCClientWithTimeout(context.Background(), time.Minute*1, c.log, endpoint) if err != nil { return fmt.Errorf("failed to dial conductor RPC: %w", err) } - c.apiClient = conductorRpc.NewAPIClient(conductorRpcClient) + c.apiClient.Value = conductorRpc.NewAPIClient(conductorRpcClient) return nil } @@ -64,7 +74,7 @@ func (c *ConductorClient) Leader(ctx context.Context) (bool, error) { return true, nil } - if err := c.initialize(); err != nil { + if err := c.initialize(ctx); err != nil { return false, err } ctx, cancel := context.WithTimeout(ctx, c.cfg.ConductorRpcTimeout) @@ -72,8 +82,11 @@ func (c *ConductorClient) Leader(ctx context.Context) (bool, error) { isLeader, err := retry.Do(ctx, 2, retry.Fixed(50*time.Millisecond), func() (bool, error) { record := c.metrics.RecordRPCClientRequest("conductor_leader") - result, err := c.apiClient.Leader(ctx) + result, err := c.apiClient.Get().Leader(ctx) record(err) + if err != nil { + c.log.Error("Failed to check conductor for leadership", "err", err) + } return result, err }) return isLeader, err @@ -85,7 +98,7 @@ func (c *ConductorClient) CommitUnsafePayload(ctx context.Context, payload *eth. return nil } - if err := c.initialize(); err != nil { + if err := c.initialize(ctx); err != nil { return err } ctx, cancel := context.WithTimeout(ctx, c.cfg.ConductorRpcTimeout) @@ -93,7 +106,7 @@ func (c *ConductorClient) CommitUnsafePayload(ctx context.Context, payload *eth. err := retry.Do0(ctx, 2, retry.Fixed(50*time.Millisecond), func() error { record := c.metrics.RecordRPCClientRequest("conductor_commitUnsafePayload") - err := c.apiClient.CommitUnsafePayload(ctx, payload) + err := c.apiClient.Get().CommitUnsafePayload(ctx, payload) record(err) return err }) @@ -107,9 +120,11 @@ func (c *ConductorClient) OverrideLeader(ctx context.Context) error { } func (c *ConductorClient) Close() { - if c.apiClient == nil { + c.apiClient.Lock() + defer c.apiClient.Unlock() + if c.apiClient.Value == nil { return } - c.apiClient.Close() - c.apiClient = nil + c.apiClient.Value.Close() + c.apiClient.Value = nil } diff --git a/op-node/node/config.go b/op-node/node/config.go index a78b55853aa25..a05047acda206 100644 --- a/op-node/node/config.go +++ b/op-node/node/config.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math" + "strings" "time" altda "github.com/ethereum-optimism/optimism/op-alt-da" @@ -12,9 +13,12 @@ import ( "github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/driver" + "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/oppprof" "github.com/ethereum/go-ethereum/log" + "github.com/ethstorage/da-server/pkg/da/client" + "github.com/urfave/cli/v2" ) type Config struct { @@ -69,13 +73,40 @@ type Config struct { // Conductor is used to determine this node is the leader sequencer. ConductorEnabled bool - ConductorRpc string + ConductorRpc ConductorRPCFunc ConductorRpcTimeout time.Duration // AltDA config AltDA altda.CLIConfig + + // DACConfig for sequencer when l2 blob is enabled + DACConfig *DACConfig +} + +func ReadDACConfigFromCLI(c *cli.Context) *DACConfig { + urls := c.String(flags.DACUrlsFlag.Name) + if urls == "" { + return nil + } + return &DACConfig{ + URLS: strings.Split(urls, ","), + } +} + +type DACConfig struct { + URLS []string } +func (dacConfig *DACConfig) Client() engine.DACClient { + if dacConfig == nil || len(dacConfig.URLS) == 0 { + return nil + } + return client.New(dacConfig.URLS) +} + +// ConductorRPCFunc retrieves the endpoint. The RPC may not immediately be available. +type ConductorRPCFunc func(ctx context.Context) (string, error) + type RPCConfig struct { ListenAddr string ListenPort int @@ -177,6 +208,12 @@ func (cfg *Config) Check() error { if cfg.AltDA.Enabled { log.Warn("Alt-DA Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues.") } + if cfg.Driver.SequencerEnabled && cfg.Rollup.IsL2BlobTimeSet() && cfg.DACConfig == nil { + return fmt.Errorf("dac.urls must be set for sequencer when l2 blob time is set") + } + if (!cfg.Driver.SequencerEnabled || !cfg.Rollup.IsL2BlobTimeSet()) && cfg.DACConfig != nil { + return fmt.Errorf("dac.urls can only be set for sequencer when l2 blob time is set") + } return nil } diff --git a/op-node/node/config_persistence.go b/op-node/node/config_persistence.go index 7a30c11b9c9ce..3f2b8b47537e9 100644 --- a/op-node/node/config_persistence.go +++ b/op-node/node/config_persistence.go @@ -55,6 +55,7 @@ func (p *ActiveConfigPersistence) SequencerStopped() error { func (p *ActiveConfigPersistence) persist(sequencerStarted bool) error { p.lock.Lock() defer p.lock.Unlock() + data, err := json.Marshal(persistedState{SequencerStarted: &sequencerStarted}) if err != nil { return fmt.Errorf("marshall new config: %w", err) diff --git a/op-node/node/node.go b/op-node/node/node.go index 298c98aa2b184..fe75ddcab2b5a 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + gosync "sync" "sync/atomic" "time" @@ -60,6 +61,7 @@ type OpNode struct { l2Source *sources.EngineClient // L2 Execution Engine RPC bindings server *rpcServer // RPC server hosting the rollup-node API p2pNode *p2p.NodeP2P // P2P node functionality + p2pMu gosync.Mutex // protects p2pNode p2pSigner p2p.Signer // p2p gossip application messages will be signed with this signer tracer Tracer // tracer to get events for testing/debugging runCfg *RuntimeConfig // runtime configurables @@ -424,8 +426,10 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config) error { } else { n.safeDB = safedb.Disabled } + + dacClient := cfg.DACConfig.Client() n.l2Driver = driver.NewDriver(n.eventSys, n.eventDrain, &cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, - n.supervisor, n.beacon, n, n, n.log, n.metrics, cfg.ConfigPersistence, n.safeDB, &cfg.Sync, sequencerConductor, altDA) + n.supervisor, n.beacon, n, n, n.log, n.metrics, cfg.ConfigPersistence, n.safeDB, &cfg.Sync, sequencerConductor, altDA, dacClient) return nil } @@ -434,8 +438,9 @@ func (n *OpNode) initRPCServer(cfg *Config) error { if err != nil { return err } - if n.p2pEnabled() { - server.EnableP2P(p2p.NewP2PAPIBackend(n.p2pNode, n.log, n.metrics)) + + if p2pNode := n.getP2PNodeIfEnabled(); p2pNode != nil { + server.EnableP2P(p2p.NewP2PAPIBackend(p2pNode, n.log, n.metrics)) } if cfg.RPC.EnableAdmin { server.EnableAdminAPI(NewAdminAPI(n.l2Driver, n.metrics, n.log)) @@ -445,6 +450,7 @@ func (n *OpNode) initRPCServer(cfg *Config) error { if err := server.Start(); err != nil { return fmt.Errorf("unable to start RPC server: %w", err) } + n.log.Info("Started JSON-RPC server", "addr", server.Addr()) n.server = server return nil } @@ -486,6 +492,8 @@ func (n *OpNode) p2pEnabled() bool { } func (n *OpNode) initP2P(cfg *Config) (err error) { + n.p2pMu.Lock() + defer n.p2pMu.Unlock() if n.p2pNode != nil { panic("p2p node already initialized") } @@ -579,13 +587,13 @@ func (n *OpNode) PublishL2Payload(ctx context.Context, envelope *eth.ExecutionPa n.tracer.OnPublishL2Payload(ctx, envelope) // publish to p2p, if we are running p2p at all - if n.p2pEnabled() { + if p2pNode := n.getP2PNodeIfEnabled(); p2pNode != nil { payload := envelope.ExecutionPayload if n.p2pSigner == nil { return fmt.Errorf("node has no p2p signer, payload %s cannot be published", payload.ID()) } n.log.Info("Publishing signed execution payload on p2p", "id", payload.ID()) - return n.p2pNode.GossipOut().PublishL2Payload(ctx, envelope, n.p2pSigner) + return p2pNode.GossipOut().PublishL2Payload(ctx, envelope, n.p2pSigner) } // if p2p is not enabled then we just don't publish the payload return nil @@ -593,7 +601,7 @@ func (n *OpNode) PublishL2Payload(ctx context.Context, envelope *eth.ExecutionPa func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, envelope *eth.ExecutionPayloadEnvelope) error { // ignore if it's from ourselves - if n.p2pEnabled() && from == n.p2pNode.Host().ID() { + if p2pNode := n.getP2PNodeIfEnabled(); p2pNode != nil && from == p2pNode.Host().ID() { return nil } @@ -614,7 +622,7 @@ func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, envelope * } func (n *OpNode) RequestL2Range(ctx context.Context, start, end eth.L2BlockRef) error { - if n.p2pEnabled() && n.p2pNode.AltSyncEnabled() { + if p2pNode := n.getP2PNodeIfEnabled(); p2pNode != nil && p2pNode.AltSyncEnabled() { if unixTimeStale(start.Time, 12*time.Hour) { n.log.Debug( "ignoring request to sync L2 range, timestamp is too old for p2p", @@ -623,7 +631,7 @@ func (n *OpNode) RequestL2Range(ctx context.Context, start, end eth.L2BlockRef) "start_time", start.Time) return nil } - return n.p2pNode.RequestL2Range(ctx, start, end) + return p2pNode.RequestL2Range(ctx, start, end) } n.log.Debug("ignoring request to sync L2 range, no sync method available", "start", start, "end", end) return nil @@ -635,7 +643,7 @@ func unixTimeStale(timestamp uint64, duration time.Duration) bool { } func (n *OpNode) P2P() p2p.Node { - return n.p2pNode + return n.getP2PNodeIfEnabled() } func (n *OpNode) RuntimeConfig() ReadonlyRuntimeConfig { @@ -670,6 +678,8 @@ func (n *OpNode) Stop(ctx context.Context) error { result = multierror.Append(result, fmt.Errorf("error stopping sequencer: %w", err)) } } + + n.p2pMu.Lock() if n.p2pNode != nil { if err := n.p2pNode.Close(); err != nil { result = multierror.Append(result, fmt.Errorf("failed to close p2p node: %w", err)) @@ -677,6 +687,8 @@ func (n *OpNode) Stop(ctx context.Context) error { // Prevent further use of p2p. n.p2pNode = nil } + n.p2pMu.Unlock() + if n.p2pSigner != nil { if err := n.p2pSigner.Close(); err != nil { result = multierror.Append(result, fmt.Errorf("failed to close p2p signer: %w", err)) @@ -777,3 +789,13 @@ func (n *OpNode) HTTPEndpoint() string { } return fmt.Sprintf("http://%s", n.server.Addr().String()) } + +func (n *OpNode) getP2PNodeIfEnabled() *p2p.NodeP2P { + if !n.p2pEnabled() { + return nil + } + + n.p2pMu.Lock() + defer n.p2pMu.Unlock() + return n.p2pNode +} diff --git a/op-node/node/server_test.go b/op-node/node/server_test.go index f8722e2723189..49697e52694eb 100644 --- a/op-node/node/server_test.go +++ b/op-node/node/server_test.go @@ -109,7 +109,7 @@ func TestOutputAtBlock(t *testing.T) { require.NoError(t, server.Stop(context.Background())) }() - client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialBackoff(3)) + client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialAttempts(3)) require.NoError(t, err) var out *eth.OutputResponse @@ -145,7 +145,7 @@ func TestVersion(t *testing.T) { require.NoError(t, server.Stop(context.Background())) }() - client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialBackoff(3)) + client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialAttempts(3)) assert.NoError(t, err) var out string @@ -191,7 +191,7 @@ func TestSyncStatus(t *testing.T) { require.NoError(t, server.Stop(context.Background())) }() - client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialBackoff(3)) + client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialAttempts(3)) assert.NoError(t, err) var out *eth.SyncStatus @@ -234,7 +234,7 @@ func TestSafeHeadAtL1Block(t *testing.T) { require.NoError(t, server.Stop(context.Background())) }() - client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialBackoff(3)) + client, err := rpcclient.NewRPC(context.Background(), log, "http://"+server.Addr().String(), rpcclient.WithDialAttempts(3)) require.NoError(t, err) var out *eth.SafeHeadResponse diff --git a/op-node/p2p/cli/load_signer.go b/op-node/p2p/cli/load_signer.go index 7416fa76397a6..3c0c532edb20d 100644 --- a/op-node/p2p/cli/load_signer.go +++ b/op-node/p2p/cli/load_signer.go @@ -5,18 +5,18 @@ import ( "strings" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" "github.com/ethereum-optimism/optimism/op-node/flags" "github.com/ethereum-optimism/optimism/op-node/p2p" + opsigner "github.com/ethereum-optimism/optimism/op-service/signer" ) -// TODO: implement remote signer setup (config to authenticated endpoint) -// and remote signer itself (e.g. a open http client to make signing requests) - // LoadSignerSetup loads a configuration for a Signer to be set up later -func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { +func LoadSignerSetup(ctx *cli.Context, logger log.Logger) (p2p.SignerSetup, error) { key := ctx.String(flags.SequencerP2PKeyName) + signerCfg := opsigner.ReadCLIConfig(ctx) if key != "" { // Mnemonics are bad because they leak *all* keys when they leak. // Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions). @@ -26,9 +26,13 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { } return &p2p.PreparedSigner{Signer: p2p.NewLocalSigner(priv)}, nil + } else if signerCfg.Enabled() { + remoteSigner, err := p2p.NewRemoteSigner(logger, signerCfg) + if err != nil { + return nil, err + } + return &p2p.PreparedSigner{Signer: remoteSigner}, nil } - // TODO: create remote signer - return nil, nil } diff --git a/op-node/p2p/config.go b/op-node/p2p/config.go index ee21ba20fc395..10a75881b87d3 100644 --- a/op-node/p2p/config.go +++ b/op-node/p2p/config.go @@ -29,7 +29,6 @@ var DefaultBootnodes = []*enode.Node{ // OP Labs enode.MustParse("enode://869d07b5932f17e8490990f75a3f94195e9504ddb6b85f7189e5a9c0a8fff8b00aecf6f3ac450ecba6cdabdb5858788a94bde2b613e0f2d82e9b395355f76d1a@34.65.67.101:30305"), enode.MustParse("enode://2d4e7e9d48f4dd4efe9342706dd1b0024681bd4c3300d021f86fc75eab7865d4e0cbec6fbc883f011cfd6a57423e7e2f6e104baad2b744c3cafaec6bc7dc92c1@34.65.43.171:30305"), - enode.MustParse("enode://9d7a3efefe442351217e73b3a593bcb8efffb55b4807699972145324eab5e6b382152f8d24f6301baebbfb5ecd4127bd3faab2842c04cd432bdf50ba092f6645@34.65.109.126:30305"), // Base enode.MustParse("enr:-J24QNz9lbrKbN4iSmmjtnr7SjUMk4zB7f1krHZcTZx-JRKZd0kA2gjufUROD6T3sOWDVDnFJRvqBBo62zuF-hYCohOGAYiOoEyEgmlkgnY0gmlwhAPniryHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQKNVFlCxh_B-716tTs-h1vMzZkSs1FTu_OYTNjgufplG4N0Y3CCJAaDdWRwgiQG"), enode.MustParse("enr:-J24QH-f1wt99sfpHy4c0QJM-NfmsIfmlLAMMcgZCUEgKG_BBYFc6FwYgaMJMQN5dsRBJApIok0jFn-9CS842lGpLmqGAYiOoDRAgmlkgnY0gmlwhLhIgb2Hb3BzdGFja4OFQgCJc2VjcDI1NmsxoQJ9FTIv8B9myn1MWaC_2lJ-sMoeCDkusCsk4BYHjjCq04N0Y3CCJAaDdWRwgiQG"), @@ -37,7 +36,9 @@ var DefaultBootnodes = []*enode.Node{ enode.MustParse("enr:-J24QHmGyBwUZXIcsGYMaUqGGSl4CFdx9Tozu-vQCn5bHIQbR7On7dZbU61vYvfrJr30t0iahSqhc64J46MnUO2JvQaGAYiOoCKKgmlkgnY0gmlwhAPnCzSHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQINc4fSijfbNIiGhcgvwjsjxVFJHUstK9L1T8OTKUjgloN0Y3CCJAaDdWRwgiQG"), enode.MustParse("enr:-J24QG3ypT4xSu0gjb5PABCmVxZqBjVw9ca7pvsI8jl4KATYAnxBmfkaIuEqy9sKvDHKuNCsy57WwK9wTt2aQgcaDDyGAYiOoGAXgmlkgnY0gmlwhDbGmZaHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQIeAK_--tcLEiu7HvoUlbV52MspE0uCocsx1f_rYvRenIN0Y3CCJAaDdWRwgiQG"), // Conduit - enode.MustParse("enode://9d7a3efefe442351217e73b3a593bcb8efffb55b4807699972145324eab5e6b382152f8d24f6301baebbfb5ecd4127bd3faab2842c04cd432bdf50ba092f6645@34.65.109.126:30305"), + enode.MustParse("enode://d25ce99435982b04d60c4b41ba256b84b888626db7bee45a9419382300fbe907359ae5ef250346785bff8d3b9d07cd3e017a27e2ee3cfda3bcbb0ba762ac9674@bootnode.conduit.xyz:0?discport=30301"), + enode.MustParse("enode://2d4e7e9d48f4dd4efe9342706dd1b0024681bd4c3300d021f86fc75eab7865d4e0cbec6fbc883f011cfd6a57423e7e2f6e104baad2b744c3cafaec6bc7dc92c1@34.65.43.171:0?discport=30305"), + enode.MustParse("enode://9d7a3efefe442351217e73b3a593bcb8efffb55b4807699972145324eab5e6b382152f8d24f6301baebbfb5ecd4127bd3faab2842c04cd432bdf50ba092f6645@34.65.109.126:0?discport=30305"), } type HostMetrics interface { diff --git a/op-node/p2p/gossip.go b/op-node/p2p/gossip.go index f835dfb2f163d..3efb5824f36d6 100644 --- a/op-node/p2p/gossip.go +++ b/op-node/p2p/gossip.go @@ -319,7 +319,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti now := uint64(time.Now().Unix()) // [REJECT] if the `payload.timestamp` is older than 60 seconds in the past - if uint64(payload.Timestamp) < now-60 { + if uint64(payload.Timestamp) < now-60 || uint64(payload.Timestamp) < cfg.BlockTime /* ensure timestamp>=BlockTime since we'll do subtraction below */ { log.Warn("payload is too old", "timestamp", uint64(payload.Timestamp)) return pubsub.ValidationReject } @@ -368,13 +368,13 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti if blockVersion.HasBlobProperties() { // [REJECT] if the block is on a topic >= V3 and has a blob gas used value that is not zero - if payload.BlobGasUsed == nil || *payload.BlobGasUsed != 0 { + if payload.BlobGasUsed == nil || (!cfg.IsL2Blob(uint64(payload.Timestamp)-cfg.BlockTime) && *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 } // [REJECT] if the block is on a topic >= V3 and has an excess blob gas value that is not zero - if payload.ExcessBlobGas == nil || *payload.ExcessBlobGas != 0 { + if payload.ExcessBlobGas == nil || (!cfg.IsL2Blob(uint64(payload.Timestamp)-cfg.BlockTime) && *payload.ExcessBlobGas != 0) { log.Warn("payload is on v3 topic, but has non-zero excess blob gas", "bad_hash", payload.BlockHash.String(), "excess_blob_gas", payload.ExcessBlobGas) return pubsub.ValidationReject } diff --git a/op-node/p2p/gossip_test.go b/op-node/p2p/gossip_test.go index 0833f270e40b4..9f047f4b5ba8f 100644 --- a/op-node/p2p/gossip_test.go +++ b/op-node/p2p/gossip_test.go @@ -3,31 +3,33 @@ package p2p import ( "bytes" "context" + "crypto/ecdsa" "fmt" "io" "math/big" "testing" "time" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" - "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/golang/snappy" - // "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + opsigner "github.com/ethereum-optimism/optimism/op-service/signer" + "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" - - "github.com/ethereum-optimism/optimism/op-service/testlog" ) func TestGuardGossipValidator(t *testing.T) { @@ -62,30 +64,122 @@ func TestVerifyBlockSignature(t *testing.T) { L2ChainID: big.NewInt(100), } peerId := peer.ID("foo") - secrets, err := e2eutils.DefaultMnemonicConfig.Secrets() + secrets, err := crypto.GenerateKey() + require.NoError(t, err) + msg := []byte("any msg") + + t.Run("Valid", func(t *testing.T) { + runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)} + signer := &PreparedSigner{Signer: NewLocalSigner(secrets)} + sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) + require.NoError(t, err) + result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) + require.Equal(t, pubsub.ValidationAccept, result) + }) + + t.Run("WrongSigner", func(t *testing.T) { + runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: common.HexToAddress("0x1234")} + signer := &PreparedSigner{Signer: NewLocalSigner(secrets)} + sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) + require.NoError(t, err) + result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) + require.Equal(t, pubsub.ValidationReject, result) + }) + + t.Run("InvalidSignature", func(t *testing.T) { + runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)} + sig := make([]byte, 65) + result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig, msg) + require.Equal(t, pubsub.ValidationReject, result) + }) + + t.Run("NoSequencer", func(t *testing.T) { + runCfg := &testutils.MockRuntimeConfig{} + signer := &PreparedSigner{Signer: NewLocalSigner(secrets)} + sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) + require.NoError(t, err) + result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) + require.Equal(t, pubsub.ValidationIgnore, result) + }) +} + +type mockRemoteSigner struct { + priv *ecdsa.PrivateKey +} + +func (t *mockRemoteSigner) SignBlockPayload(args opsigner.BlockPayloadArgs) (hexutil.Bytes, error) { + signingHash, err := args.ToSigningHash() + if err != nil { + return nil, err + } + signature, err := crypto.Sign(signingHash[:], t.priv) + if err != nil { + return nil, err + } + return signature, nil +} + +func TestVerifyBlockSignatureWithRemoteSigner(t *testing.T) { + secrets, err := crypto.GenerateKey() require.NoError(t, err) + + remoteSigner := &mockRemoteSigner{secrets} + server := oprpc.NewServer( + "127.0.0.1", + 0, + "test", + oprpc.WithAPIs([]rpc.API{ + { + Namespace: "opsigner", + Service: remoteSigner, + }, + }), + ) + + require.NoError(t, server.Start()) + defer func() { + _ = server.Stop() + }() + + logger := testlog.Logger(t, log.LevelCrit) + cfg := &rollup.Config{ + L2ChainID: big.NewInt(100), + } + + peerId := peer.ID("foo") msg := []byte("any msg") + signerCfg := opsigner.NewCLIConfig() + signerCfg.Endpoint = fmt.Sprintf("http://%s", server.Endpoint()) + signerCfg.TLSConfig.TLSKey = "" + signerCfg.TLSConfig.TLSCert = "" + signerCfg.TLSConfig.TLSCaCert = "" + signerCfg.TLSConfig.Enabled = false + t.Run("Valid", func(t *testing.T) { - runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)} - signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} + runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)} + remoteSigner, err := NewRemoteSigner(logger, signerCfg) + require.NoError(t, err) + signer := &PreparedSigner{Signer: remoteSigner} sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) require.NoError(t, err) - result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:65], msg) + result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationAccept, result) }) t.Run("WrongSigner", func(t *testing.T) { runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: common.HexToAddress("0x1234")} - signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} + remoteSigner, err := NewRemoteSigner(logger, signerCfg) + require.NoError(t, err) + signer := &PreparedSigner{Signer: remoteSigner} sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) require.NoError(t, err) - result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:65], msg) + result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationReject, result) }) t.Run("InvalidSignature", func(t *testing.T) { - runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)} + runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)} sig := make([]byte, 65) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig, msg) require.Equal(t, pubsub.ValidationReject, result) @@ -93,12 +187,36 @@ func TestVerifyBlockSignature(t *testing.T) { t.Run("NoSequencer", func(t *testing.T) { runCfg := &testutils.MockRuntimeConfig{} - signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} + remoteSigner, err := NewRemoteSigner(logger, signerCfg) + require.NoError(t, err) + signer := &PreparedSigner{Signer: remoteSigner} sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) require.NoError(t, err) - result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:65], msg) + result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationIgnore, result) }) + + t.Run("RemoteSignerNoTLS", func(t *testing.T) { + signerCfg := opsigner.NewCLIConfig() + signerCfg.Endpoint = fmt.Sprintf("http://%s", server.Endpoint()) + signerCfg.TLSConfig.TLSKey = "invalid" + signerCfg.TLSConfig.TLSCert = "invalid" + signerCfg.TLSConfig.TLSCaCert = "invalid" + signerCfg.TLSConfig.Enabled = true + + _, err := NewRemoteSigner(logger, signerCfg) + require.Error(t, err) + }) + + t.Run("RemoteSignerInvalidEndpoint", func(t *testing.T) { + signerCfg := opsigner.NewCLIConfig() + signerCfg.Endpoint = "Invalid" + signerCfg.TLSConfig.TLSKey = "" + signerCfg.TLSConfig.TLSCert = "" + signerCfg.TLSConfig.TLSCaCert = "" + _, err := NewRemoteSigner(logger, signerCfg) + require.Error(t, err) + }) } type MarshalSSZ interface { @@ -146,10 +264,10 @@ func TestBlockValidator(t *testing.T) { cfg := &rollup.Config{ L2ChainID: big.NewInt(100), } - secrets, err := e2eutils.DefaultMnemonicConfig.Secrets() + secrets, err := crypto.GenerateKey() require.NoError(t, err) - runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)} - signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} + runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)} + signer := &PreparedSigner{Signer: NewLocalSigner(secrets)} // Params Set 2: Call the validation function peerID := peer.ID("foo") diff --git a/op-node/p2p/host_test.go b/op-node/p2p/host_test.go index 2bab3239e55e5..85b0c9e605880 100644 --- a/op-node/p2p/host_test.go +++ b/op-node/p2p/host_test.go @@ -6,6 +6,7 @@ import ( "math/big" "net" "slices" + gosync "sync" "testing" "time" @@ -126,9 +127,12 @@ func TestP2PFull(t *testing.T) { conns := make(chan network.Conn, 1) hostA := nodeA.Host() + var once gosync.Once hostA.Network().Notify(&network.NotifyBundle{ ConnectedF: func(n network.Network, conn network.Conn) { - conns <- conn + once.Do(func() { + conns <- conn + }) }}) backend := NewP2PAPIBackend(nodeA, logA, nil) diff --git a/op-node/p2p/signer.go b/op-node/p2p/signer.go index cd5e9f94a01b8..20a52d0a2625f 100644 --- a/op-node/p2p/signer.go +++ b/op-node/p2p/signer.go @@ -9,8 +9,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-node/rollup" + opsigner "github.com/ethereum-optimism/optimism/op-service/signer" ) var SigningDomainBlocksV1 = [32]byte{} @@ -20,40 +22,27 @@ type Signer interface { io.Closer } -func SigningHash(domain [32]byte, chainID *big.Int, payloadBytes []byte) (common.Hash, error) { - var msgInput [32 + 32 + 32]byte - // domain: first 32 bytes - copy(msgInput[:32], domain[:]) - // chain_id: second 32 bytes - if chainID.BitLen() > 256 { - return common.Hash{}, errors.New("chain_id is too large") - } - chainID.FillBytes(msgInput[32:64]) - // payload_hash: third 32 bytes, hash of encoded payload - copy(msgInput[64:], crypto.Keccak256(payloadBytes)) - - return crypto.Keccak256Hash(msgInput[:]), nil -} - func BlockSigningHash(cfg *rollup.Config, payloadBytes []byte) (common.Hash, error) { - return SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes) + return opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).ToSigningHash() } // LocalSigner is suitable for testing type LocalSigner struct { - priv *ecdsa.PrivateKey - hasher func(domain [32]byte, chainID *big.Int, payloadBytes []byte) (common.Hash, error) + priv *ecdsa.PrivateKey } func NewLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner { - return &LocalSigner{priv: priv, hasher: SigningHash} + return &LocalSigner{priv: priv} } func (s *LocalSigner) Sign(ctx context.Context, domain [32]byte, chainID *big.Int, encodedMsg []byte) (sig *[65]byte, err error) { if s.priv == nil { return nil, errors.New("signer is closed") } - signingHash, err := s.hasher(domain, chainID, encodedMsg) + + blockPayloadArgs := opsigner.NewBlockPayloadArgs(domain, chainID, encodedMsg, nil) + signingHash, err := blockPayloadArgs.ToSigningHash() + if err != nil { return nil, err } @@ -69,6 +58,39 @@ func (s *LocalSigner) Close() error { return nil } +type RemoteSigner struct { + client *opsigner.SignerClient + sender *common.Address +} + +func NewRemoteSigner(logger log.Logger, config opsigner.CLIConfig) (*RemoteSigner, error) { + signerClient, err := opsigner.NewSignerClientFromConfig(logger, config) + if err != nil { + return nil, err + } + senderAddress := common.HexToAddress(config.Address) + return &RemoteSigner{signerClient, &senderAddress}, nil +} + +func (s *RemoteSigner) Sign(ctx context.Context, domain [32]byte, chainID *big.Int, encodedMsg []byte) (sig *[65]byte, err error) { + if s.client == nil { + return nil, errors.New("signer is closed") + } + + blockPayloadArgs := opsigner.NewBlockPayloadArgs(domain, chainID, encodedMsg, s.sender) + signature, err := s.client.SignBlockPayload(ctx, blockPayloadArgs) + + if err != nil { + return nil, err + } + return &signature, nil +} + +func (s *RemoteSigner) Close() error { + s.client = nil + return nil +} + type PreparedSigner struct { Signer } diff --git a/op-node/p2p/signer_test.go b/op-node/p2p/signer_test.go index abcfe4825b307..53f78504d18fb 100644 --- a/op-node/p2p/signer_test.go +++ b/op-node/p2p/signer_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-node/rollup" + opsigner "github.com/ethereum-optimism/optimism/op-service/signer" "github.com/stretchr/testify/require" ) @@ -14,10 +15,10 @@ func TestSigningHash_DifferentDomain(t *testing.T) { } payloadBytes := []byte("arbitraryData") - hash, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes) + hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).ToSigningHash() require.NoError(t, err, "creating first signing hash") - hash2, err := SigningHash([32]byte{3}, cfg.L2ChainID, payloadBytes) + hash2, err := opsigner.NewBlockPayloadArgs([32]byte{3}, cfg.L2ChainID, payloadBytes, nil).ToSigningHash() require.NoError(t, err, "creating second signing hash") require.NotEqual(t, hash, hash2, "signing hash should be different when domain is different") @@ -32,10 +33,10 @@ func TestSigningHash_DifferentChainID(t *testing.T) { } payloadBytes := []byte("arbitraryData") - hash, err := SigningHash(SigningDomainBlocksV1, cfg1.L2ChainID, payloadBytes) + hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg1.L2ChainID, payloadBytes, nil).ToSigningHash() require.NoError(t, err, "creating first signing hash") - hash2, err := SigningHash(SigningDomainBlocksV1, cfg2.L2ChainID, payloadBytes) + hash2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg2.L2ChainID, payloadBytes, nil).ToSigningHash() require.NoError(t, err, "creating second signing hash") require.NotEqual(t, hash, hash2, "signing hash should be different when chain ID is different") @@ -46,10 +47,10 @@ func TestSigningHash_DifferentMessage(t *testing.T) { L2ChainID: big.NewInt(100), } - hash, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg1")) + hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg1"), nil).ToSigningHash() require.NoError(t, err, "creating first signing hash") - hash2, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg2")) + hash2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg2"), nil).ToSigningHash() require.NoError(t, err, "creating second signing hash") require.NotEqual(t, hash, hash2, "signing hash should be different when message is different") @@ -62,6 +63,6 @@ func TestSigningHash_LimitChainID(t *testing.T) { cfg := &rollup.Config{ L2ChainID: chainID, } - _, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, []byte("arbitraryData")) + _, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("arbitraryData"), nil).ToSigningHash() require.ErrorContains(t, err, "chain_id is too large") } diff --git a/op-node/p2p/sync.go b/op-node/p2p/sync.go index c8777fe51f928..1b2cc570a2e97 100644 --- a/op-node/p2p/sync.go +++ b/op-node/p2p/sync.go @@ -346,6 +346,9 @@ func (s *SyncClient) AddPeer(id peer.ID) { func (s *SyncClient) RemovePeer(id peer.ID) { s.peersLock.Lock() defer s.peersLock.Unlock() + if s.closingPeers { + return + } cancel, ok := s.peers[id] if !ok { s.log.Warn("cannot remove peer from sync duties, peer was not registered", "peer", id) diff --git a/op-node/p2p/sync_test.go b/op-node/p2p/sync_test.go index 7c0b3350df040..2527f48fbc655 100644 --- a/op-node/p2p/sync_test.go +++ b/op-node/p2p/sync_test.go @@ -360,18 +360,24 @@ func TestNetworkNotifyAddPeerAndRemovePeer(t *testing.T) { return nil }, metrics.NoopMetrics, &NoopApplicationScorer{}) - waitChan := make(chan struct{}, 1) + waitChan := make(chan struct{}, 2) + var connectedOnce sync.Once + var disconnectedOnce sync.Once hostA.Network().Notify(&network.NotifyBundle{ ConnectedF: func(nw network.Network, conn network.Conn) { - syncCl.AddPeer(conn.RemotePeer()) - waitChan <- struct{}{} + connectedOnce.Do(func() { + syncCl.AddPeer(conn.RemotePeer()) + waitChan <- struct{}{} + }) }, DisconnectedF: func(nw network.Network, conn network.Conn) { - // only when no connection is available, we can remove the peer - if nw.Connectedness(conn.RemotePeer()) == network.NotConnected { - syncCl.RemovePeer(conn.RemotePeer()) - } - waitChan <- struct{}{} + disconnectedOnce.Do(func() { + // only when no connection is available, we can remove the peer + if nw.Connectedness(conn.RemotePeer()) == network.NotConnected { + syncCl.RemovePeer(conn.RemotePeer()) + } + waitChan <- struct{}{} + }) }, }) syncCl.Start() diff --git a/op-node/rollup/attributes/attributes.go b/op-node/rollup/attributes/attributes.go index 4ebb27050882b..819bc50c24ca1 100644 --- a/op-node/rollup/attributes/attributes.go +++ b/op-node/rollup/attributes/attributes.go @@ -62,6 +62,7 @@ func (eq *AttributesHandler) OnEvent(ev event.Event) bool { eq.onPendingSafeUpdate(x) case derive.DerivedAttributesEvent: eq.attributes = x.Attributes + eq.sentAttributes = false eq.emitter.Emit(derive.ConfirmReceivedAttributesEvent{}) // to make sure we have a pre-state signal to process the attributes from eq.emitter.Emit(engine.PendingSafeRequestEvent{}) @@ -71,7 +72,7 @@ func (eq *AttributesHandler) OnEvent(ev event.Event) bool { case rollup.EngineTemporaryErrorEvent: eq.sentAttributes = false case engine.InvalidPayloadAttributesEvent: - if x.Attributes.DerivedFrom == (eth.L1BlockRef{}) { + if !x.Attributes.IsDerived() { return true // from sequencing } eq.sentAttributes = false @@ -193,7 +194,7 @@ func (eq *AttributesHandler) consolidateNextSafeAttributes(attributes *derive.At } eq.emitter.Emit(engine.PromotePendingSafeEvent{ Ref: ref, - Safe: attributes.IsLastInSpan, + Concluding: attributes.Concluding, DerivedFrom: attributes.DerivedFrom, }) } diff --git a/op-node/rollup/attributes/attributes_test.go b/op-node/rollup/attributes/attributes_test.go index c3ed171ce92f2..05fbd21fe784e 100644 --- a/op-node/rollup/attributes/attributes_test.go +++ b/op-node/rollup/attributes/attributes_test.go @@ -117,9 +117,9 @@ func TestAttributesHandler(t *testing.T) { NoTxPool: false, GasLimit: &payloadA1.ExecutionPayload.GasLimit, }, - Parent: refA0, - IsLastInSpan: true, - DerivedFrom: refB, + Parent: refA0, + Concluding: true, + DerivedFrom: refB, } refA1, err := derive.PayloadToBlockRef(cfg, payloadA1.ExecutionPayload) require.NoError(t, err) @@ -154,9 +154,9 @@ func TestAttributesHandler(t *testing.T) { NoTxPool: false, GasLimit: &payloadA1Alt.ExecutionPayload.GasLimit, }, - Parent: refA0, - IsLastInSpan: true, - DerivedFrom: refBAlt, + Parent: refA0, + Concluding: true, + DerivedFrom: refBAlt, } refA1Alt, err := derive.PayloadToBlockRef(cfg, payloadA1Alt.ExecutionPayload) @@ -272,7 +272,7 @@ func TestAttributesHandler(t *testing.T) { require.Nil(t, ah.attributes, "drop when attributes are successful") }) t.Run("consolidation passes", func(t *testing.T) { - fn := func(t *testing.T, lastInSpan bool) { + fn := func(t *testing.T, concluding bool) { logger := testlog.Logger(t, log.LevelInfo) l2 := &testutils.MockL2Client{} emitter := &testutils.MockEmitter{} @@ -280,10 +280,10 @@ func TestAttributesHandler(t *testing.T) { ah.AttachEmitter(emitter) attr := &derive.AttributesWithParent{ - Attributes: attrA1.Attributes, // attributes will match, passing consolidation - Parent: attrA1.Parent, - IsLastInSpan: lastInSpan, - DerivedFrom: refB, + Attributes: attrA1.Attributes, // attributes will match, passing consolidation + Parent: attrA1.Parent, + Concluding: concluding, + DerivedFrom: refB, } emitter.ExpectOnce(derive.ConfirmReceivedAttributesEvent{}) emitter.ExpectOnce(engine.PendingSafeRequestEvent{}) @@ -296,7 +296,7 @@ func TestAttributesHandler(t *testing.T) { emitter.ExpectOnce(engine.PromotePendingSafeEvent{ Ref: refA1, - Safe: lastInSpan, // last in span becomes safe instantaneously + Concluding: concluding, DerivedFrom: refB, }) ah.OnEvent(engine.PendingSafeUpdateEvent{ @@ -340,7 +340,7 @@ func TestAttributesHandler(t *testing.T) { require.NotNil(t, ah.attributes, "queued up derived attributes") // sanity check test setup - require.True(t, attrA1Alt.IsLastInSpan, "must be last in span for attributes to become safe") + require.True(t, attrA1Alt.Concluding, "must be concluding attributes") // attrA1Alt will fit right on top of A0 emitter.ExpectOnce(engine.BuildStartEvent{Attributes: attrA1Alt}) @@ -396,5 +396,4 @@ func TestAttributesHandler(t *testing.T) { l2.AssertExpectations(t) emitter.AssertExpectations(t) }) - } diff --git a/op-node/rollup/chain_spec.go b/op-node/rollup/chain_spec.go index 66d2e526d0d12..1ddcb31902904 100644 --- a/op-node/rollup/chain_spec.go +++ b/op-node/rollup/chain_spec.go @@ -1,6 +1,7 @@ package rollup import ( + "fmt" "math/big" "github.com/ethereum-optimism/optimism/op-node/params" @@ -41,19 +42,47 @@ const ( Granite ForkName = "granite" Holocene ForkName = "holocene" Interop ForkName = "interop" - None ForkName = "none" + // ADD NEW FORKS TO AllForks BELOW! + None ForkName = "none" ) -var nextFork = map[ForkName]ForkName{ - Bedrock: Regolith, - Regolith: Canyon, - Canyon: Delta, - Delta: Ecotone, - Ecotone: Fjord, - Fjord: Granite, - Granite: Holocene, - Holocene: Interop, - Interop: None, +var AllForks = []ForkName{ + Bedrock, + Regolith, + Canyon, + Delta, + Ecotone, + Fjord, + Granite, + Holocene, + Interop, + // ADD NEW FORKS HERE! +} + +func ForksFrom(fork ForkName) []ForkName { + for i, f := range AllForks { + if f == fork { + return AllForks[i:] + } + } + panic(fmt.Sprintf("invalid fork: %s", fork)) +} + +var nextFork = func() map[ForkName]ForkName { + m := make(map[ForkName]ForkName, len(AllForks)) + for i, f := range AllForks { + if i == len(AllForks)-1 { + m[f] = None + break + } + m[f] = AllForks[i+1] + } + return m +}() + +func IsValidFork(fork ForkName) bool { + _, ok := nextFork[fork] + return ok } type ChainSpec struct { @@ -80,6 +109,11 @@ func (s *ChainSpec) IsCanyon(t uint64) bool { return s.config.IsCanyon(t) } +// IsHolocene returns true if t >= holocene_time +func (s *ChainSpec) IsHolocene(t uint64) bool { + return s.config.IsHolocene(t) +} + // MaxChannelBankSize returns the maximum number of bytes the can allocated inside the channel bank // before pruning occurs at the given timestamp. func (s *ChainSpec) MaxChannelBankSize(t uint64) uint64 { diff --git a/op-node/rollup/clsync/clsync.go b/op-node/rollup/clsync/clsync.go index 64193b21b110d..abefd63a2297c 100644 --- a/op-node/rollup/clsync/clsync.go +++ b/op-node/rollup/clsync/clsync.go @@ -88,7 +88,7 @@ func (eq *CLSync) OnEvent(ev event.Event) bool { // onInvalidPayload checks if the first next-up payload matches the invalid payload. // If so, the payload is dropped, to give the next payloads a try. func (eq *CLSync) onInvalidPayload(x engine.PayloadInvalidEvent) { - eq.log.Debug("CL sync received invalid-payload report", x.Envelope.ExecutionPayload.ID()) + eq.log.Debug("CL sync received invalid-payload report", "id", x.Envelope.ExecutionPayload.ID()) block := x.Envelope.ExecutionPayload if peek := eq.unsafePayloads.Peek(); peek != nil && diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index cc38a31b9c54f..5d859a9fcd596 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -28,6 +28,8 @@ type FetchingAttributesBuilder struct { rollupCfg *rollup.Config l1 L1ReceiptsFetcher l2 SystemConfigL2Fetcher + // whether to skip the L1 origin timestamp check - only for testing purposes + testSkipL1OriginCheck bool } func NewFetchingAttributesBuilder(rollupCfg *rollup.Config, l1 L1ReceiptsFetcher, l2 SystemConfigL2Fetcher) *FetchingAttributesBuilder { @@ -38,6 +40,12 @@ func NewFetchingAttributesBuilder(rollupCfg *rollup.Config, l1 L1ReceiptsFetcher } } +// TestSkipL1OriginCheck skips the L1 origin timestamp check for testing purposes. +// Must not be used in production! +func (ba *FetchingAttributesBuilder) TestSkipL1OriginCheck() { + ba.testSkipL1OriginCheck = true +} + // PreparePayloadAttributes prepares a PayloadAttributes template that is ready to build a L2 block with deposits only, on top of the given l2Parent, with the given epoch as L1 origin. // The template defaults to NoTxPool=true, and no sequencer transactions: the caller has to modify the template to add transactions, // by setting NoTxPool=false as sequencer, or by appending batch transactions as verifier. @@ -93,9 +101,9 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex seqNumber = l2Parent.SequenceNumber + 1 } - // Sanity check the L1 origin was correctly selected to maintain the time invariant between L1 and L2 nextL2Time := l2Parent.Time + ba.rollupCfg.BlockTime - if nextL2Time < l1Info.Time() { + // Sanity check the L1 origin was correctly selected to maintain the time invariant between L1 and L2 + if !ba.testSkipL1OriginCheck && nextL2Time < l1Info.Time() { return nil, NewResetError(fmt.Errorf("cannot build L2 block on top %s for time %d before L1 origin %s at time %d", l2Parent, nextL2Time, eth.ToBlockID(l1Info), l1Info.Time())) } @@ -149,7 +157,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex } } - return ð.PayloadAttributes{ + r := ð.PayloadAttributes{ Timestamp: hexutil.Uint64(nextL2Time), PrevRandao: eth.Bytes32(l1Info.MixDigest()), SuggestedFeeRecipient: predeploys.SequencerFeeVaultAddr, @@ -158,5 +166,11 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex GasLimit: (*eth.Uint64Quantity)(&sysConfig.GasLimit), Withdrawals: withdrawals, ParentBeaconBlockRoot: parentBeaconRoot, - }, nil + } + if ba.rollupCfg.IsHolocene(nextL2Time) { + r.EIP1559Params = new(eth.Bytes8) + *r.EIP1559Params = sysConfig.EIP1559Params + } + + return r, nil } diff --git a/op-node/rollup/derive/attributes_queue.go b/op-node/rollup/derive/attributes_queue.go index c15079c6bfa9f..dba39ef742ff3 100644 --- a/op-node/rollup/derive/attributes_queue.go +++ b/op-node/rollup/derive/attributes_queue.go @@ -2,6 +2,7 @@ package derive import ( "context" + "errors" "fmt" "io" "time" @@ -28,23 +29,44 @@ type AttributesBuilder interface { } type AttributesWithParent struct { - Attributes *eth.PayloadAttributes - Parent eth.L2BlockRef - IsLastInSpan bool + Attributes *eth.PayloadAttributes + Parent eth.L2BlockRef + Concluding bool // Concluding indicates that the attributes conclude the pending safe phase DerivedFrom eth.L1BlockRef } +// WithDepositsOnly return a shallow clone with all non-Deposit transactions +// stripped from the transactions of its attributes. The order is preserved. +func (a *AttributesWithParent) WithDepositsOnly() *AttributesWithParent { + clone := *a + clone.Attributes = clone.Attributes.WithDepositsOnly() + return &clone +} + +func (a *AttributesWithParent) IsDerived() bool { + return a.DerivedFrom != (eth.L1BlockRef{}) +} + type AttributesQueue struct { - log log.Logger - config *rollup.Config - builder AttributesBuilder - prev *BatchQueue - batch *SingularBatch - isLastInSpan bool + log log.Logger + config *rollup.Config + builder AttributesBuilder + prev SingularBatchProvider + + batch *SingularBatch + concluding bool + lastAttribs *AttributesWithParent } -func NewAttributesQueue(log log.Logger, cfg *rollup.Config, builder AttributesBuilder, prev *BatchQueue) *AttributesQueue { +type SingularBatchProvider interface { + ResettableStage + ChannelFlusher + Origin() eth.L1BlockRef + NextBatch(context.Context, eth.L2BlockRef) (*SingularBatch, bool, error) +} + +func NewAttributesQueue(log log.Logger, cfg *rollup.Config, builder AttributesBuilder, prev SingularBatchProvider) *AttributesQueue { return &AttributesQueue{ log: log, config: cfg, @@ -60,12 +82,12 @@ func (aq *AttributesQueue) Origin() eth.L1BlockRef { func (aq *AttributesQueue) NextAttributes(ctx context.Context, parent eth.L2BlockRef) (*AttributesWithParent, error) { // Get a batch if we need it if aq.batch == nil { - batch, isLastInSpan, err := aq.prev.NextBatch(ctx, parent) + batch, concluding, err := aq.prev.NextBatch(ctx, parent) if err != nil { return nil, err } aq.batch = batch - aq.isLastInSpan = isLastInSpan + aq.concluding = concluding } // Actually generate the next attributes @@ -74,16 +96,16 @@ func (aq *AttributesQueue) NextAttributes(ctx context.Context, parent eth.L2Bloc } else { // Clear out the local state once we will succeed attr := AttributesWithParent{ - Attributes: attrs, - Parent: parent, - IsLastInSpan: aq.isLastInSpan, - DerivedFrom: aq.Origin(), + Attributes: attrs, + Parent: parent, + Concluding: aq.concluding, + DerivedFrom: aq.Origin(), } + aq.lastAttribs = &attr aq.batch = nil - aq.isLastInSpan = false + aq.concluding = false return &attr, nil } - } // createNextAttributes transforms a batch into a payload attributes. This sets `NoTxPool` and appends the batched transactions @@ -114,8 +136,35 @@ func (aq *AttributesQueue) createNextAttributes(ctx context.Context, batch *Sing return attrs, nil } -func (aq *AttributesQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error { +func (aq *AttributesQueue) reset() { aq.batch = nil - aq.isLastInSpan = false // overwritten later, but set for consistency + aq.concluding = false // overwritten later, but set for consistency + aq.lastAttribs = nil +} + +func (aq *AttributesQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error { + aq.reset() return io.EOF } + +func (aq *AttributesQueue) DepositsOnlyAttributes(parent eth.BlockID, derivedFrom eth.L1BlockRef) (*AttributesWithParent, error) { + // Sanity checks - these cannot happen with correct deriver implementations. + if aq.batch != nil { + return nil, fmt.Errorf("unexpected buffered batch, parent hash: %s, epoch: %s", aq.batch.ParentHash, aq.batch.Epoch()) + } else if aq.lastAttribs == nil { + return nil, errors.New("no attributes generated yet") + } else if derivedFrom != aq.lastAttribs.DerivedFrom { + return nil, fmt.Errorf( + "unexpected derivation origin, last_origin: %s, invalid_origin: %s", + aq.lastAttribs.DerivedFrom, derivedFrom) + } else if parent != aq.lastAttribs.Parent.ID() { + return nil, fmt.Errorf( + "unexpected parent: last_parent: %s, invalid_parent: %s", + aq.lastAttribs.Parent.ID(), parent) + } + + aq.prev.FlushChannel() // flush all channel data in previous stages + attrs := aq.lastAttribs.WithDepositsOnly() + aq.lastAttribs = attrs + return attrs, nil +} diff --git a/op-node/rollup/derive/attributes_test.go b/op-node/rollup/derive/attributes_test.go index 26b4b1f284370..cf42e81696b46 100644 --- a/op-node/rollup/derive/attributes_test.go +++ b/op-node/rollup/derive/attributes_test.go @@ -120,6 +120,7 @@ func TestPreparePayloadAttributes(t *testing.T) { attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch) require.NoError(t, err) require.NotNil(t, attrs) + require.Nil(t, attrs.EIP1559Params) // should be nil prior to Holocene require.Equal(t, l2Parent.Time+cfg.BlockTime, uint64(attrs.Timestamp)) require.Equal(t, eth.Bytes32(l1Info.InfoMixDigest), attrs.PrevRandao) require.Equal(t, predeploys.SequencerFeeVaultAddr, attrs.SuggestedFeeRecipient) @@ -287,6 +288,36 @@ func TestPreparePayloadAttributes(t *testing.T) { require.True(t, attrs.NoTxPool) }) + t.Run("holocene 1559 params", func(t *testing.T) { + cfg.ActivateAtGenesis(rollup.Holocene) + rng := rand.New(rand.NewSource(1234)) + l1Fetcher := &testutils.MockL1Source{} + defer l1Fetcher.AssertExpectations(t) + l2Parent := testutils.RandomL2BlockRef(rng) + l1CfgFetcher := &testutils.MockL2Client{} + eip1559Params := eth.Bytes8([]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}) + testSysCfg := eth.SystemConfig{ + BatcherAddr: common.Address{42}, + Overhead: [32]byte{}, + Scalar: [32]byte{}, + EIP1559Params: eip1559Params, + } + l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil) + defer l1CfgFetcher.AssertExpectations(t) + l1Info := testutils.RandomBlockInfo(rng) + l1Info.InfoParentHash = l2Parent.L1Origin.Hash + l1Info.InfoNum = l2Parent.L1Origin.Number + 1 + epoch := l1Info.ID() + l1InfoTx, err := L1InfoDepositBytes(cfg, testSysCfg, 0, l1Info, 0) + require.NoError(t, err) + l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil) + attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher) + attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch) + require.NoError(t, err) + require.Equal(t, eip1559Params, *attrs.EIP1559Params) + require.Equal(t, l1InfoTx, []byte(attrs.Transactions[0])) + }) + // Test that the payload attributes builder changes the deposit format based on L2-time-based regolith activation t.Run("regolith", func(t *testing.T) { testCases := []struct { diff --git a/op-node/rollup/derive/batch_mux.go b/op-node/rollup/derive/batch_mux.go new file mode 100644 index 0000000000000..ea8336a59b1ee --- /dev/null +++ b/op-node/rollup/derive/batch_mux.go @@ -0,0 +1,76 @@ +package derive + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/slices" +) + +// BatchMux multiplexes between different batch stages. +// Stages are swapped on demand during Reset calls, or explicitly with Transform. +// It currently chooses the BatchQueue pre-Holocene and the BatchStage post-Holocene. +type BatchMux struct { + log log.Logger + cfg *rollup.Config + prev NextBatchProvider + l2 SafeBlockFetcher + + // embedded active stage + SingularBatchProvider +} + +var _ SingularBatchProvider = (*BatchMux)(nil) + +// NewBatchMux returns an uninitialized BatchMux. Reset has to be called before +// calling other methods, to activate the right stage for a given L1 origin. +func NewBatchMux(lgr log.Logger, cfg *rollup.Config, prev NextBatchProvider, l2 SafeBlockFetcher) *BatchMux { + return &BatchMux{log: lgr, cfg: cfg, prev: prev, l2: l2} +} + +func (b *BatchMux) Reset(ctx context.Context, base eth.L1BlockRef, sysCfg eth.SystemConfig) error { + // TODO(12490): change to a switch over b.cfg.ActiveFork(base.Time) + switch { + default: + if _, ok := b.SingularBatchProvider.(*BatchQueue); !ok { + b.log.Info("BatchMux: activating pre-Holocene stage during reset", "origin", base) + b.SingularBatchProvider = NewBatchQueue(b.log, b.cfg, b.prev, b.l2) + } + case b.cfg.IsHolocene(base.Time): + if _, ok := b.SingularBatchProvider.(*BatchStage); !ok { + b.log.Info("BatchMux: activating Holocene stage during reset", "origin", base) + b.SingularBatchProvider = NewBatchStage(b.log, b.cfg, b.prev, b.l2) + } + } + return b.SingularBatchProvider.Reset(ctx, base, sysCfg) +} + +func (b *BatchMux) Transform(f rollup.ForkName) { + switch f { + case rollup.Holocene: + b.TransformHolocene() + } +} + +func (b *BatchMux) TransformHolocene() { + switch bp := b.SingularBatchProvider.(type) { + case *BatchQueue: + b.log.Info("BatchMux: transforming to Holocene stage") + bs := NewBatchStage(b.log, b.cfg, b.prev, b.l2) + // Even though any ongoing span batch or queued batches are dropped at Holocene activation, the + // post-Holocene batch stage still needs access to the collected l1Blocks pre-Holocene because + // the first Holocene channel will contain pre-Holocene batches. + bs.l1Blocks = slices.Clone(bp.l1Blocks) + bs.origin = bp.origin + b.SingularBatchProvider = bs + case *BatchStage: + // Even if the pipeline is Reset to the activation block, the previous origin will be the + // same, so transfromStages isn't called. + panic(fmt.Sprintf("Holocene BatchStage already active, old origin: %v", bp.Origin())) + default: + panic(fmt.Sprintf("unknown batch stage type: %T", bp)) + } +} diff --git a/op-node/rollup/derive/batch_mux_test.go b/op-node/rollup/derive/batch_mux_test.go new file mode 100644 index 0000000000000..2afc25a69dc2c --- /dev/null +++ b/op-node/rollup/derive/batch_mux_test.go @@ -0,0 +1,68 @@ +package derive + +import ( + "context" + "io" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestBatchMux_LaterHolocene(t *testing.T) { + log := testlog.Logger(t, log.LevelTrace) + ctx := context.Background() + l1A := eth.L1BlockRef{Time: 0, Hash: common.Hash{0xaa}} + l1B := eth.L1BlockRef{Time: 12, Hash: common.Hash{0xbb}} + cfg := &rollup.Config{ + HoloceneTime: &l1B.Time, + } + b := NewBatchMux(log, cfg, nil, nil) + + require.Nil(t, b.SingularBatchProvider) + + err := b.Reset(ctx, l1A, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(BatchQueue), b.SingularBatchProvider) + require.Equal(t, l1A, b.SingularBatchProvider.(*BatchQueue).origin) + + b.Transform(rollup.Holocene) + require.IsType(t, new(BatchStage), b.SingularBatchProvider) + require.Equal(t, l1A, b.SingularBatchProvider.(*BatchStage).origin) + + err = b.Reset(ctx, l1B, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(BatchStage), b.SingularBatchProvider) + require.Equal(t, l1B, b.SingularBatchProvider.(*BatchStage).origin) + + err = b.Reset(ctx, l1A, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(BatchQueue), b.SingularBatchProvider) + require.Equal(t, l1A, b.SingularBatchProvider.(*BatchQueue).origin) +} + +func TestBatchMux_ActiveHolocene(t *testing.T) { + log := testlog.Logger(t, log.LevelTrace) + ctx := context.Background() + l1A := eth.L1BlockRef{Time: 42, Hash: common.Hash{0xaa}} + cfg := &rollup.Config{ + HoloceneTime: &l1A.Time, + } + // without the fake input, the panic check later would panic because of the Origin() call + prev := &fakeBatchQueueInput{origin: l1A} + b := NewBatchMux(log, cfg, prev, nil) + + require.Nil(t, b.SingularBatchProvider) + + err := b.Reset(ctx, l1A, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(BatchStage), b.SingularBatchProvider) + require.Equal(t, l1A, b.SingularBatchProvider.(*BatchStage).origin) + + require.Panics(t, func() { b.Transform(rollup.Holocene) }) +} diff --git a/op-node/rollup/derive/batch_queue.go b/op-node/rollup/derive/batch_queue.go index 4ca49907c7de9..3029d1199767a 100644 --- a/op-node/rollup/derive/batch_queue.go +++ b/op-node/rollup/derive/batch_queue.go @@ -28,6 +28,7 @@ import ( // working range are not considered or pruned. type NextBatchProvider interface { + ChannelFlusher Origin() eth.L1BlockRef NextBatch(ctx context.Context) (Batch, error) } @@ -37,12 +38,15 @@ type SafeBlockFetcher interface { PayloadByNumber(context.Context, uint64) (*eth.ExecutionPayloadEnvelope, error) } -// BatchQueue contains a set of batches for every L1 block. -// L1 blocks are contiguous and this does not support reorgs. -type BatchQueue struct { +// The baseBatchStage is a shared implementation of basic channel stage functionality. It is +// currently shared between the legacy BatchQueue, which buffers future batches, and the +// post-Holocene BatchStage, which requires strictly ordered batches. +type baseBatchStage struct { log log.Logger config *rollup.Config prev NextBatchProvider + l2 SafeBlockFetcher + origin eth.L1BlockRef // l1Blocks contains consecutive eth.L1BlockRef sorted by time. @@ -53,18 +57,12 @@ type BatchQueue struct { // length of l1Blocks never exceeds SequencerWindowSize l1Blocks []eth.L1BlockRef - // batches in order of when we've first seen them - batches []*BatchWithL1InclusionBlock - // nextSpan is cached SingularBatches derived from SpanBatch nextSpan []*SingularBatch - - l2 SafeBlockFetcher } -// NewBatchQueue creates a BatchQueue, which should be Reset(origin) before use. -func NewBatchQueue(log log.Logger, cfg *rollup.Config, prev NextBatchProvider, l2 SafeBlockFetcher) *BatchQueue { - return &BatchQueue{ +func newBaseBatchStage(log log.Logger, cfg *rollup.Config, prev NextBatchProvider, l2 SafeBlockFetcher) baseBatchStage { + return baseBatchStage{ log: log, config: cfg, prev: prev, @@ -72,80 +70,125 @@ func NewBatchQueue(log log.Logger, cfg *rollup.Config, prev NextBatchProvider, l } } -func (bq *BatchQueue) Origin() eth.L1BlockRef { - return bq.prev.Origin() +func (bs *baseBatchStage) base() *baseBatchStage { + return bs +} + +func (bs *baseBatchStage) Log() log.Logger { + if len(bs.l1Blocks) == 0 { + return bs.log.New("origin", bs.origin.ID()) + } else { + return bs.log.New("origin", bs.origin.ID(), "epoch", bs.l1Blocks[0]) + } +} + +// BatchQueue contains a set of batches for every L1 block. +// L1 blocks are contiguous and this does not support reorgs. +type BatchQueue struct { + baseBatchStage + + // batches in order of when we've first seen them + batches []*BatchWithL1InclusionBlock +} + +var _ SingularBatchProvider = (*BatchQueue)(nil) + +// NewBatchQueue creates a BatchQueue, which should be Reset(origin) before use. +func NewBatchQueue(log log.Logger, cfg *rollup.Config, prev NextBatchProvider, l2 SafeBlockFetcher) *BatchQueue { + return &BatchQueue{baseBatchStage: newBaseBatchStage(log, cfg, prev, l2)} +} + +func (bs *baseBatchStage) Origin() eth.L1BlockRef { + return bs.prev.Origin() } // popNextBatch pops the next batch from the current queued up span-batch nextSpan. // The queue must be non-empty, or the function will panic. -func (bq *BatchQueue) popNextBatch(parent eth.L2BlockRef) *SingularBatch { - if len(bq.nextSpan) == 0 { +func (bs *baseBatchStage) popNextBatch(parent eth.L2BlockRef) *SingularBatch { + if len(bs.nextSpan) == 0 { panic("popping non-existent span-batch, invalid state") } - nextBatch := bq.nextSpan[0] - bq.nextSpan = bq.nextSpan[1:] + nextBatch := bs.nextSpan[0] + bs.nextSpan = bs.nextSpan[1:] // Must set ParentHash before return. we can use parent because the parentCheck is verified in CheckBatch(). nextBatch.ParentHash = parent.Hash - bq.log.Debug("pop next batch from the cached span batch") + bs.log.Debug("pop next batch from the cached span batch") return nextBatch } // NextBatch return next valid batch upon the given safe head. // It also returns the boolean that indicates if the batch is the last block in the batch. -func (bq *BatchQueue) NextBatch(ctx context.Context, parent eth.L2BlockRef) (*SingularBatch, bool, error) { - if len(bq.nextSpan) > 0 { +func (bs *baseBatchStage) nextFromSpanBatch(parent eth.L2BlockRef) (*SingularBatch, bool) { + if len(bs.nextSpan) > 0 { // There are cached singular batches derived from the span batch. // Check if the next cached batch matches the given parent block. - if bq.nextSpan[0].Timestamp == parent.Time+bq.config.BlockTime { + if bs.nextSpan[0].Timestamp == parent.Time+bs.config.BlockTime { // Pop first one and return. - nextBatch := bq.popNextBatch(parent) + nextBatch := bs.popNextBatch(parent) // len(bq.nextSpan) == 0 means it's the last batch of the span. - return nextBatch, len(bq.nextSpan) == 0, nil + return nextBatch, len(bs.nextSpan) == 0 } else { // Given parent block does not match the next batch. It means the previously returned batch is invalid. // Drop cached batches and find another batch. - bq.log.Warn("parent block does not match the next batch. dropped cached batches", "parent", parent.ID(), "nextBatchTime", bq.nextSpan[0].GetTimestamp()) - bq.nextSpan = bq.nextSpan[:0] + bs.log.Warn("parent block does not match the next batch. dropped cached batches", "parent", parent.ID(), "nextBatchTime", bs.nextSpan[0].GetTimestamp()) + bs.nextSpan = bs.nextSpan[:0] } } + return nil, false +} +func (bs *baseBatchStage) updateOrigins(parent eth.L2BlockRef) { // Note: We use the origin that we will have to determine if it's behind. This is important // because it's the future origin that gets saved into the l1Blocks array. // We always update the origin of this stage if it is not the same so after the update code // runs, this is consistent. - originBehind := bq.prev.Origin().Number < parent.L1Origin.Number + originBehind := bs.originBehind(parent) // Advance origin if needed // Note: The entire pipeline has the same origin // We just don't accept batches prior to the L1 origin of the L2 safe head - if bq.origin != bq.prev.Origin() { - bq.origin = bq.prev.Origin() + if bs.origin != bs.prev.Origin() { + bs.origin = bs.prev.Origin() if !originBehind { - bq.l1Blocks = append(bq.l1Blocks, bq.origin) + bs.l1Blocks = append(bs.l1Blocks, bs.origin) } else { // This is to handle the special case of startup. At startup we call Reset & include // the L1 origin. That is the only time where immediately after `Reset` is called // originBehind is false. - bq.l1Blocks = bq.l1Blocks[:0] + bs.l1Blocks = bs.l1Blocks[:0] } - bq.log.Info("Advancing bq origin", "origin", bq.origin, "originBehind", originBehind) + bs.log.Info("Advancing bq origin", "origin", bs.origin, "originBehind", originBehind) } // If the epoch is advanced, update bq.l1Blocks - // Advancing epoch must be done after the pipeline successfully apply the entire span batch to the chain. - // Because the span batch can be reverted during processing the batch, then we must preserve existing l1Blocks - // to verify the epochs of the next candidate batch. - if len(bq.l1Blocks) > 0 && parent.L1Origin.Number > bq.l1Blocks[0].Number { - for i, l1Block := range bq.l1Blocks { + // Before Holocene, advancing the epoch must be done after the pipeline successfully applied the entire span batch to the chain. + // This is because the entire span batch can be reverted after finding an invalid batch. + // So we must preserve the existing l1Blocks to verify the epochs of the next candidate batch. + if len(bs.l1Blocks) > 0 && parent.L1Origin.Number > bs.l1Blocks[0].Number { + for i, l1Block := range bs.l1Blocks { if parent.L1Origin.Number == l1Block.Number { - bq.l1Blocks = bq.l1Blocks[i:] - bq.log.Debug("Advancing internal L1 blocks", "next_epoch", bq.l1Blocks[0].ID(), "next_epoch_time", bq.l1Blocks[0].Time) + bs.l1Blocks = bs.l1Blocks[i:] + bs.log.Debug("Advancing internal L1 blocks", "next_epoch", bs.l1Blocks[0].ID(), "next_epoch_time", bs.l1Blocks[0].Time) break } } // If we can't find the origin of parent block, we have to advance bq.origin. } +} +func (bs *baseBatchStage) originBehind(parent eth.L2BlockRef) bool { + return bs.prev.Origin().Number < parent.L1Origin.Number +} + +func (bq *BatchQueue) NextBatch(ctx context.Context, parent eth.L2BlockRef) (*SingularBatch, bool, error) { + // Early return if there are singular batches from a span batch queued up + if batch, last := bq.nextFromSpanBatch(parent); batch != nil { + return batch, last, nil + } + + bq.updateOrigins(parent) + + originBehind := bq.originBehind(parent) // Load more data into the batch queue outOfData := false if batch, err := bq.prev.NextBatch(ctx); err == io.EOF { @@ -206,20 +249,30 @@ func (bq *BatchQueue) NextBatch(ctx context.Context, parent eth.L2BlockRef) (*Si return nextBatch, len(bq.nextSpan) == 0, nil } -func (bq *BatchQueue) Reset(ctx context.Context, base eth.L1BlockRef, _ eth.SystemConfig) error { +func (bs *baseBatchStage) reset(base eth.L1BlockRef) { // Copy over the Origin from the next stage // It is set in the engine queue (two stages away) such that the L2 Safe Head origin is the progress - bq.origin = base - bq.batches = []*BatchWithL1InclusionBlock{} + bs.origin = base + bs.l1Blocks = bs.l1Blocks[:0] // Include the new origin as an origin to build on // Note: This is only for the initialization case. During normal resets we will later // throw out this block. - bq.l1Blocks = bq.l1Blocks[:0] - bq.l1Blocks = append(bq.l1Blocks, base) - bq.nextSpan = bq.nextSpan[:0] + bs.l1Blocks = append(bs.l1Blocks, base) + bs.nextSpan = bs.nextSpan[:0] +} + +func (bq *BatchQueue) Reset(_ context.Context, base eth.L1BlockRef, _ eth.SystemConfig) error { + bq.baseBatchStage.reset(base) + bq.batches = bq.batches[:0] return io.EOF } +func (bq *BatchQueue) FlushChannel() { + // We need to implement the ChannelFlusher interface with the BatchQueue but it's never called + // of which the BatchMux takes care. + panic("BatchQueue: invalid FlushChannel call") +} + func (bq *BatchQueue) AddBatch(ctx context.Context, batch Batch, parent eth.L2BlockRef) { if len(bq.l1Blocks) == 0 { panic(fmt.Errorf("cannot add batch with timestamp %d, no origin was prepared", batch.GetTimestamp())) @@ -257,7 +310,6 @@ func (bq *BatchQueue) deriveNextBatch(ctx context.Context, outOfData bool, paren // Find the first-seen batch that matches all validity conditions. // We may not have sufficient information to proceed filtering, and then we stop. // There may be none: in that case we force-create an empty batch - nextTimestamp := parent.Time + bq.config.BlockTime var nextBatch *BatchWithL1InclusionBlock // Go over all batches, in order of inclusion, and find the first batch we can accept. @@ -296,33 +348,39 @@ batchLoop: nextBatch.Batch.LogContext(bq.log).Info("Found next batch") return nextBatch.Batch, nil } + return bq.deriveNextEmptyBatch(ctx, outOfData, parent) +} +// deriveNextEmptyBatch may derive an empty batch if the sequencing window is expired +func (bs *baseBatchStage) deriveNextEmptyBatch(ctx context.Context, outOfData bool, parent eth.L2BlockRef) (*SingularBatch, error) { + epoch := bs.l1Blocks[0] // If the current epoch is too old compared to the L1 block we are at, // i.e. if the sequence window expired, we create empty batches for the current epoch - expiryEpoch := epoch.Number + bq.config.SeqWindowSize - forceEmptyBatches := (expiryEpoch == bq.origin.Number && outOfData) || expiryEpoch < bq.origin.Number + expiryEpoch := epoch.Number + bs.config.SeqWindowSize + forceEmptyBatches := (expiryEpoch == bs.origin.Number && outOfData) || expiryEpoch < bs.origin.Number firstOfEpoch := epoch.Number == parent.L1Origin.Number+1 + nextTimestamp := parent.Time + bs.config.BlockTime - bq.log.Trace("Potentially generating an empty batch", + bs.log.Trace("Potentially generating an empty batch", "expiryEpoch", expiryEpoch, "forceEmptyBatches", forceEmptyBatches, "nextTimestamp", nextTimestamp, - "epoch_time", epoch.Time, "len_l1_blocks", len(bq.l1Blocks), "firstOfEpoch", firstOfEpoch) + "epoch_time", epoch.Time, "len_l1_blocks", len(bs.l1Blocks), "firstOfEpoch", firstOfEpoch) if !forceEmptyBatches { // sequence window did not expire yet, still room to receive batches for the current epoch, // no need to force-create empty batch(es) towards the next epoch yet. return nil, io.EOF } - if len(bq.l1Blocks) < 2 { + if len(bs.l1Blocks) < 2 { // need next L1 block to proceed towards return nil, io.EOF } - nextEpoch := bq.l1Blocks[1] + nextEpoch := bs.l1Blocks[1] // Fill with empty L2 blocks of the same epoch until we meet the time of the next L1 origin, // to preserve that L2 time >= L1 time. If this is the first block of the epoch, always generate a // batch to ensure that we at least have one batch per epoch. if nextTimestamp < nextEpoch.Time || firstOfEpoch { - bq.log.Info("Generating next batch", "epoch", epoch, "timestamp", nextTimestamp) + bs.log.Info("Generating next batch", "epoch", epoch, "timestamp", nextTimestamp) return &SingularBatch{ ParentHash: parent.Hash, EpochNum: rollup.Epoch(epoch.Number), @@ -334,7 +392,9 @@ batchLoop: // At this point we have auto generated every batch for the current epoch // that we can, so we can advance to the next epoch. - bq.log.Trace("Advancing internal L1 blocks", "next_timestamp", nextTimestamp, "next_epoch_time", nextEpoch.Time) - bq.l1Blocks = bq.l1Blocks[1:] + // TODO(12444): Instead of manually advancing the epoch here, it may be better to generate a + // batch for the next epoch, so that updateOrigins then properly advances the origin. + bs.log.Trace("Advancing internal L1 blocks", "next_timestamp", nextTimestamp, "next_epoch_time", nextEpoch.Time) + bs.l1Blocks = bs.l1Blocks[1:] return nil, io.EOF } diff --git a/op-node/rollup/derive/batch_queue_test.go b/op-node/rollup/derive/batch_queue_test.go index f047f0a7d4fef..54ae9abc63703 100644 --- a/op-node/rollup/derive/batch_queue_test.go +++ b/op-node/rollup/derive/batch_queue_test.go @@ -32,6 +32,12 @@ func (f *fakeBatchQueueInput) Origin() eth.L1BlockRef { return f.origin } +func (f *fakeBatchQueueInput) FlushChannel() { + f.batches = nil + f.errors = nil + f.i = 0 +} + func (f *fakeBatchQueueInput) NextBatch(ctx context.Context) (Batch, error) { if f.i >= len(f.batches) { return nil, io.EOF @@ -141,33 +147,66 @@ func TestBatchQueue(t *testing.T) { name string f func(t *testing.T, batchType int) }{ - {"BatchQueueNewOrigin", BatchQueueNewOrigin}, - {"BatchQueueEager", BatchQueueEager}, - {"BatchQueueInvalidInternalAdvance", BatchQueueInvalidInternalAdvance}, - {"BatchQueueMissing", BatchQueueMissing}, - {"BatchQueueAdvancedEpoch", BatchQueueAdvancedEpoch}, - {"BatchQueueShuffle", BatchQueueShuffle}, - {"BatchQueueResetOneBlockBeforeOrigin", BatchQueueResetOneBlockBeforeOrigin}, + {"Missing", testBatchQueue_Missing}, + {"Shuffle", testBatchQueue_Shuffle}, } for _, test := range tests { test := test t.Run(test.name+"_SingularBatch", func(t *testing.T) { test.f(t, SingularBatchType) }) + t.Run(test.name+"_SpanBatch", func(t *testing.T) { + test.f(t, SpanBatchType) + }) } +} +type testableBatchStageFactory func(log.Logger, *rollup.Config, NextBatchProvider, SafeBlockFetcher) testableBatchStage + +type testableBatchStage interface { + SingularBatchProvider + base() *baseBatchStage +} + +func TestBatchStages(t *testing.T) { + newBatchQueue := func(log log.Logger, cfg *rollup.Config, prev NextBatchProvider, l2 SafeBlockFetcher) testableBatchStage { + return NewBatchQueue(log, cfg, prev, l2) + } + newBatchStage := func(log log.Logger, cfg *rollup.Config, prev NextBatchProvider, l2 SafeBlockFetcher) testableBatchStage { + return NewBatchStage(log, cfg, prev, l2) + } + + tests := []struct { + name string + f func(*testing.T, int, testableBatchStageFactory) + }{ + {"NewOrigin", testBatchStage_NewOrigin}, + {"Eager", testBatchStage_Eager}, + {"InvalidInternalAdvance", testBatchStage_InvalidInternalAdvance}, + {"AdvancedEpoch", testBatchStage_AdvancedEpoch}, + {"ResetOneBlockBeforeOrigin", testBatchStage_ResetOneBlockBeforeOrigin}, + } for _, test := range tests { test := test - t.Run(test.name+"_SpanBatch", func(t *testing.T) { - test.f(t, SpanBatchType) + t.Run("BatchQueue_"+test.name+"_SingularBatch", func(t *testing.T) { + test.f(t, SingularBatchType, newBatchQueue) + }) + t.Run("BatchQueue_"+test.name+"_SpanBatch", func(t *testing.T) { + test.f(t, SpanBatchType, newBatchQueue) + }) + t.Run("BatchStage_"+test.name+"_SingularBatch", func(t *testing.T) { + test.f(t, SingularBatchType, newBatchStage) + }) + t.Run("BatchStage_"+test.name+"_SpanBatch", func(t *testing.T) { + test.f(t, SpanBatchType, newBatchStage) }) } } -// BatchQueueNewOrigin tests that the batch queue properly saves the new origin +// testBatchStage_NewOrigin tests that the batch queue properly saves the new origin // when the safehead's origin is ahead of the pipeline's origin (as is after a reset). // This issue was fixed in https://github.com/ethereum-optimism/optimism/pull/3694 -func BatchQueueNewOrigin(t *testing.T, batchType int) { +func testBatchStage_NewOrigin(t *testing.T, batchType int, newBatchStage testableBatchStageFactory) { log := testlog.Logger(t, log.LevelCrit) l1 := L1Chain([]uint64{10, 15, 20, 25}) safeHead := eth.L2BlockRef{ @@ -194,17 +233,18 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) { origin: l1[0], } - bq := NewBatchQueue(log, cfg, input, nil) + bq := newBatchStage(log, cfg, input, nil) + bqb := bq.base() _ = bq.Reset(context.Background(), l1[0], eth.SystemConfig{}) - require.Equal(t, []eth.L1BlockRef{l1[0]}, bq.l1Blocks) + require.Equal(t, []eth.L1BlockRef{l1[0]}, bqb.l1Blocks) // Prev Origin: 0; Safehead Origin: 2; Internal Origin: 0 // Should return no data but keep the same origin data, _, err := bq.NextBatch(context.Background(), safeHead) require.Nil(t, data) require.Equal(t, io.EOF, err) - require.Equal(t, []eth.L1BlockRef{l1[0]}, bq.l1Blocks) - require.Equal(t, l1[0], bq.origin) + require.Equal(t, []eth.L1BlockRef{l1[0]}, bqb.l1Blocks) + require.Equal(t, l1[0], bqb.origin) // Prev Origin: 1; Safehead Origin: 2; Internal Origin: 0 // Should wipe l1blocks + advance internal origin @@ -212,8 +252,8 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) { data, _, err = bq.NextBatch(context.Background(), safeHead) require.Nil(t, data) require.Equal(t, io.EOF, err) - require.Empty(t, bq.l1Blocks) - require.Equal(t, l1[1], bq.origin) + require.Empty(t, bqb.l1Blocks) + require.Equal(t, l1[1], bqb.origin) // Prev Origin: 2; Safehead Origin: 2; Internal Origin: 1 // Should add to l1Blocks + advance internal origin @@ -221,14 +261,14 @@ func BatchQueueNewOrigin(t *testing.T, batchType int) { data, _, err = bq.NextBatch(context.Background(), safeHead) require.Nil(t, data) require.Equal(t, io.EOF, err) - require.Equal(t, []eth.L1BlockRef{l1[2]}, bq.l1Blocks) - require.Equal(t, l1[2], bq.origin) + require.Equal(t, []eth.L1BlockRef{l1[2]}, bqb.l1Blocks) + require.Equal(t, l1[2], bqb.origin) } -// BatchQueueResetOneBlockBeforeOrigin tests that the batch queue properly +// testBatchStage_ResetOneBlockBeforeOrigin tests that the batch queue properly // prunes the l1Block recorded as part of a reset when the starting origin // is exactly one block prior to the safe head origin. -func BatchQueueResetOneBlockBeforeOrigin(t *testing.T, batchType int) { +func testBatchStage_ResetOneBlockBeforeOrigin(t *testing.T, batchType int, newBatchStage testableBatchStageFactory) { log := testlog.Logger(t, log.LevelTrace) l1 := L1Chain([]uint64{10, 15, 20, 25}) safeHead := eth.L2BlockRef{ @@ -255,17 +295,18 @@ func BatchQueueResetOneBlockBeforeOrigin(t *testing.T, batchType int) { origin: l1[0], } - bq := NewBatchQueue(log, cfg, input, nil) + bq := newBatchStage(log, cfg, input, nil) + bqb := bq.base() _ = bq.Reset(context.Background(), l1[0], eth.SystemConfig{}) - require.Equal(t, []eth.L1BlockRef{l1[0]}, bq.l1Blocks) + require.Equal(t, []eth.L1BlockRef{l1[0]}, bqb.l1Blocks) // Prev Origin: 0; Safehead Origin: 1; Internal Origin: 0 // Should return no data but keep the same origin data, _, err := bq.NextBatch(context.Background(), safeHead) require.Nil(t, data) require.Equal(t, io.EOF, err) - require.Equal(t, []eth.L1BlockRef{l1[0]}, bq.l1Blocks) - require.Equal(t, l1[0], bq.origin) + require.Equal(t, []eth.L1BlockRef{l1[0]}, bqb.l1Blocks) + require.Equal(t, l1[0], bqb.origin) // Prev Origin: 1; Safehead Origin: 1; Internal Origin: 0 // Should record new l1 origin in l1blocks, prune block 0 and advance internal origin @@ -273,8 +314,8 @@ func BatchQueueResetOneBlockBeforeOrigin(t *testing.T, batchType int) { data, _, err = bq.NextBatch(context.Background(), safeHead) require.Nil(t, data) require.Equalf(t, io.EOF, err, "expected io.EOF but got %v", err) - require.Equal(t, []eth.L1BlockRef{l1[1]}, bq.l1Blocks) - require.Equal(t, l1[1], bq.origin) + require.Equal(t, []eth.L1BlockRef{l1[1]}, bqb.l1Blocks) + require.Equal(t, l1[1], bqb.origin) // Prev Origin: 2; Safehead Origin: 1; Internal Origin: 1 // Should add to l1Blocks + advance internal origin @@ -282,13 +323,13 @@ func BatchQueueResetOneBlockBeforeOrigin(t *testing.T, batchType int) { data, _, err = bq.NextBatch(context.Background(), safeHead) require.Nil(t, data) require.Equal(t, io.EOF, err) - require.Equal(t, []eth.L1BlockRef{l1[1], l1[2]}, bq.l1Blocks) - require.Equal(t, l1[2], bq.origin) + require.Equal(t, []eth.L1BlockRef{l1[1], l1[2]}, bqb.l1Blocks) + require.Equal(t, l1[2], bqb.origin) } -// BatchQueueEager adds a bunch of contiguous batches and asserts that +// testBatchStage_Eager adds a bunch of contiguous batches and asserts that // enough calls to `NextBatch` return all of those batches. -func BatchQueueEager(t *testing.T, batchType int) { +func testBatchStage_Eager(t *testing.T, batchType int, newBatchStage testableBatchStageFactory) { log := testlog.Logger(t, log.LevelCrit) l1 := L1Chain([]uint64{10, 20, 30}) chainId := big.NewInt(1234) @@ -344,7 +385,7 @@ func BatchQueueEager(t *testing.T, batchType int) { origin: l1[0], } - bq := NewBatchQueue(log, cfg, input, nil) + bq := newBatchStage(log, cfg, input, nil) _ = bq.Reset(context.Background(), l1[0], eth.SystemConfig{}) // Advance the origin input.origin = l1[1] @@ -364,11 +405,11 @@ func BatchQueueEager(t *testing.T, batchType int) { } } -// BatchQueueInvalidInternalAdvance asserts that we do not miss an epoch when generating batches. +// testBatchStage_InvalidInternalAdvance asserts that we do not miss an epoch when generating batches. // This is a regression test for CLI-3378. -func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { +func testBatchStage_InvalidInternalAdvance(t *testing.T, batchType int, newBatchStage testableBatchStageFactory) { log := testlog.Logger(t, log.LevelTrace) - l1 := L1Chain([]uint64{10, 15, 20, 25, 30}) + l1 := L1Chain([]uint64{5, 10, 15, 20, 25, 30}) chainId := big.NewInt(1234) safeHead := eth.L2BlockRef{ Hash: mockHash(10, 2), @@ -416,17 +457,26 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { } } + // prepend a nil batch so we can load the safe head's epoch input := &fakeBatchQueueInput{ - batches: inputBatches, - errors: inputErrors, + batches: append([]Batch{nil}, inputBatches...), + errors: append([]error{io.EOF}, inputErrors...), origin: l1[0], } - bq := NewBatchQueue(log, cfg, input, nil) + bq := newBatchStage(log, cfg, input, nil) _ = bq.Reset(context.Background(), l1[0], eth.SystemConfig{}) + // first load base epoch + b, _, e := bq.NextBatch(context.Background(), safeHead) + require.ErrorIs(t, e, io.EOF) + require.Nil(t, b) + // then advance to origin 1 with batches + input.origin = l1[1] + // Load continuous batches for epoch 0 for i := 0; i < len(expectedOutputBatches); i++ { + t.Logf("Iteration %d", i) b, _, e := bq.NextBatch(context.Background(), safeHead) require.ErrorIs(t, e, expectedOutputErrors[i]) if b == nil { @@ -440,14 +490,7 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { } } - // Advance to origin 1. No forced batches yet. - input.origin = l1[1] - b, _, e := bq.NextBatch(context.Background(), safeHead) - require.ErrorIs(t, e, io.EOF) - require.Nil(t, b) - - // Advance to origin 2. No forced batches yet because we are still on epoch 0 - // & have batches for epoch 0. + // Advance to origin 2. No forced batches yet. input.origin = l1[2] b, _, e = bq.NextBatch(context.Background(), safeHead) require.ErrorIs(t, e, io.EOF) @@ -456,7 +499,7 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { // Advance to origin 3. Should generate one empty batch. input.origin = l1[3] b, _, e = bq.NextBatch(context.Background(), safeHead) - require.Nil(t, e) + require.NoError(t, e) require.NotNil(t, b) require.Equal(t, safeHead.Time+2, b.Timestamp) require.Equal(t, rollup.Epoch(1), b.EpochNum) @@ -471,7 +514,7 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { // Advance to origin 4. Should generate one empty batch. input.origin = l1[4] b, _, e = bq.NextBatch(context.Background(), safeHead) - require.Nil(t, e) + require.NoError(t, e) require.NotNil(t, b) require.Equal(t, rollup.Epoch(2), b.EpochNum) require.Equal(t, safeHead.Time+2, b.Timestamp) @@ -482,10 +525,9 @@ func BatchQueueInvalidInternalAdvance(t *testing.T, batchType int) { b, _, e = bq.NextBatch(context.Background(), safeHead) require.ErrorIs(t, e, io.EOF) require.Nil(t, b) - } -func BatchQueueMissing(t *testing.T, batchType int) { +func testBatchQueue_Missing(t *testing.T, batchType int) { log := testlog.Logger(t, log.LevelCrit) l1 := L1Chain([]uint64{10, 15, 20, 25}) chainId := big.NewInt(1234) @@ -600,9 +642,9 @@ func BatchQueueMissing(t *testing.T, batchType int) { require.Equal(t, rollup.Epoch(1), b.EpochNum) } -// BatchQueueAdvancedEpoch tests that batch queue derives consecutive valid batches with advancing epochs. +// testBatchStage_AdvancedEpoch tests that batch queue derives consecutive valid batches with advancing epochs. // Batch queue's l1blocks list should be updated along epochs. -func BatchQueueAdvancedEpoch(t *testing.T, batchType int) { +func testBatchStage_AdvancedEpoch(t *testing.T, batchType int, newBatchStage testableBatchStageFactory) { log := testlog.Logger(t, log.LevelCrit) l1 := L1Chain([]uint64{0, 6, 12, 18, 24}) // L1 block time: 6s chainId := big.NewInt(1234) @@ -664,7 +706,7 @@ func BatchQueueAdvancedEpoch(t *testing.T, batchType int) { origin: l1[inputOriginNumber], } - bq := NewBatchQueue(log, cfg, input, nil) + bq := newBatchStage(log, cfg, input, nil) _ = bq.Reset(context.Background(), l1[1], eth.SystemConfig{}) for i := 0; i < len(expectedOutputBatches); i++ { @@ -688,8 +730,8 @@ func BatchQueueAdvancedEpoch(t *testing.T, batchType int) { } } -// BatchQueueShuffle tests batch queue can reorder shuffled valid batches -func BatchQueueShuffle(t *testing.T, batchType int) { +// testBatchQueue_Shuffle tests batch queue can reorder shuffled valid batches +func testBatchQueue_Shuffle(t *testing.T, batchType int) { log := testlog.Logger(t, log.LevelCrit) l1 := L1Chain([]uint64{0, 6, 12, 18, 24}) // L1 block time: 6s chainId := big.NewInt(1234) diff --git a/op-node/rollup/derive/batch_stage.go b/op-node/rollup/derive/batch_stage.go new file mode 100644 index 0000000000000..d18efed2278e9 --- /dev/null +++ b/op-node/rollup/derive/batch_stage.go @@ -0,0 +1,171 @@ +package derive + +import ( + "context" + "errors" + "fmt" + "io" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/log" +) + +type BatchStage struct { + baseBatchStage +} + +var _ SingularBatchProvider = (*BatchStage)(nil) + +func NewBatchStage(log log.Logger, cfg *rollup.Config, prev NextBatchProvider, l2 SafeBlockFetcher) *BatchStage { + return &BatchStage{baseBatchStage: newBaseBatchStage(log, cfg, prev, l2)} +} + +func (bs *BatchStage) Reset(_ context.Context, base eth.L1BlockRef, _ eth.SystemConfig) error { + bs.reset(base) + return io.EOF +} + +func (bs *BatchStage) FlushChannel() { + bs.nextSpan = bs.nextSpan[:0] + bs.prev.FlushChannel() +} + +func (bs *BatchStage) NextBatch(ctx context.Context, parent eth.L2BlockRef) (*SingularBatch, bool, error) { + // with Holocene, we can always update (and prune) the origins because we don't backwards-invalidate. + bs.updateOrigins(parent) + + // If origin behind (or at parent), we drain previous stage(s), and then return. + // Note that a channel from the parent's L1 origin block can only contain past batches, so we + // can just skip them. + // TODO(12444): we may be able to change the definition of originBehind to include equality, + // also for the pre-Holocene BatchQueue. This may also allow us to remove the edge case in + // updateOrigins. + if bs.originBehind(parent) || parent.L1Origin.Number == bs.origin.Number { + if _, err := bs.prev.NextBatch(ctx); err != nil { + // includes io.EOF and NotEnoughData + return nil, false, err + } + // continue draining + return nil, false, NotEnoughData + } + + if len(bs.l1Blocks) < 2 { + // This can only happen if derivation erroneously doesn't start at a safe head. + // By now, the L1 origin of the first safe head and the following L1 block must be in the + // l1Blocks. + return nil, false, NewCriticalError(fmt.Errorf( + "unexpected low buffered origin count, origin: %v, parent: %v", bs.origin, parent)) + } + + // Note: epoch origin can now be one block ahead of the L2 Safe Head + // This is in the case where we auto generate all batches in an epoch & advance the epoch in + // deriveNextEmptyBatch but don't advance the L2 Safe Head's epoch + if epoch := bs.l1Blocks[0]; parent.L1Origin != epoch.ID() && parent.L1Origin.Number != epoch.Number-1 { + return nil, false, NewResetError(fmt.Errorf("buffered L1 chain epoch %s in batch queue does not match safe head origin %s", epoch, parent.L1Origin)) + } + + batch, err := bs.nextSingularBatchCandidate(ctx, parent) + if err == io.EOF { + // We only consider empty batch generation after we've drained all batches from the local + // span batch queue and the previous stage. + empty, err := bs.deriveNextEmptyBatch(ctx, true, parent) + // An empty batch always advances the (local) safe head. + return empty, true, err + } else if err != nil { + return nil, false, err + } + + // check candidate validity + validity := checkSingularBatch(bs.config, bs.Log(), bs.l1Blocks, parent, batch, bs.origin) + switch validity { + case BatchAccept: // continue + batch.LogContext(bs.Log()).Debug("Found next singular batch") + // BatchStage is only used with Holocene, where blocks immediately become (local) safe + return batch, true, nil + case BatchPast: + batch.LogContext(bs.Log()).Warn("Dropping past singular batch") + // NotEnoughData to read in next batch until we're through all past batches + return nil, false, NotEnoughData + case BatchDrop: // drop, flush, move onto next channel + batch.LogContext(bs.Log()).Warn("Dropping invalid singular batch, flushing channel") + bs.FlushChannel() + // NotEnoughData will cause derivation from previous stages until they're empty, at which + // point empty batch derivation will happen. + return nil, false, NotEnoughData + case BatchUndecided: // l2 fetcher error, try again + batch.LogContext(bs.Log()).Warn("Undecided span batch") + return nil, false, NotEnoughData + case BatchFuture: // panic, can't happen + return nil, false, NewCriticalError(fmt.Errorf("impossible batch validity: %v", validity)) + default: + return nil, false, NewCriticalError(fmt.Errorf("unknown batch validity type: %d", validity)) + } +} + +func (bs *BatchStage) nextSingularBatchCandidate(ctx context.Context, parent eth.L2BlockRef) (*SingularBatch, error) { + // First check for next span-derived batch + nextBatch, _ := bs.nextFromSpanBatch(parent) + + if nextBatch != nil { + return nextBatch, nil + } + + // If the next batch is a singular batch, we forward it as the candidate. + // If it is a span batch, we check its validity and then forward its first singular batch. + batch, err := bs.prev.NextBatch(ctx) + if err != nil { // includes io.EOF + return nil, err + } + switch typ := batch.GetBatchType(); typ { + case SingularBatchType: + singularBatch, ok := batch.AsSingularBatch() + if !ok { + return nil, NewCriticalError(errors.New("failed type assertion to SingularBatch")) + } + return singularBatch, nil + case SpanBatchType: + spanBatch, ok := batch.AsSpanBatch() + if !ok { + return nil, NewCriticalError(errors.New("failed type assertion to SpanBatch")) + } + + validity, _ := checkSpanBatchPrefix(ctx, bs.config, bs.Log(), bs.l1Blocks, parent, spanBatch, bs.origin, bs.l2) + switch validity { + case BatchAccept: // continue + spanBatch.LogContext(bs.Log()).Info("Found next valid span batch") + case BatchPast: + spanBatch.LogContext(bs.Log()).Warn("Dropping past span batch") + // NotEnoughData to read in next batch until we're through all past batches + return nil, NotEnoughData + case BatchDrop: // drop, try next + spanBatch.LogContext(bs.Log()).Warn("Dropping invalid span batch, flushing channel") + bs.FlushChannel() + return nil, NotEnoughData + case BatchUndecided: // l2 fetcher error, try again + spanBatch.LogContext(bs.Log()).Warn("Undecided span batch") + return nil, NotEnoughData + case BatchFuture: // can't happen with Holocene + return nil, NewCriticalError(errors.New("impossible future batch validity")) + } + + // If next batch is SpanBatch, convert it to SingularBatches. + // TODO(12444): maybe create iterator here instead, save to nextSpan + // Need to make sure this doesn't error where the iterator wouldn't, + // otherwise this wouldn't be correctly implementing partial span batch invalidation. + // From what I can tell, it is fine because the only error case is if the l1Blocks are + // missing a block, which would be a logic error. Although, if the node restarts mid-way + // through a span batch and the sync start only goes back one channel timeout from the + // mid-way safe block, it may actually miss l1 blocks! Need to check. + // We could fix this by fast-dropping past batches from the span batch. + singularBatches, err := spanBatch.GetSingularBatches(bs.l1Blocks, parent) + if err != nil { + return nil, NewCriticalError(err) + } + bs.nextSpan = singularBatches + // span-batches are non-empty, so the below pop is safe. + return bs.popNextBatch(parent), nil + default: + return nil, NewCriticalError(fmt.Errorf("unrecognized batch type: %d", typ)) + } +} diff --git a/op-node/rollup/derive/batches.go b/op-node/rollup/derive/batches.go index bde2280745520..4e3e20d1e50fc 100644 --- a/op-node/rollup/derive/batches.go +++ b/op-node/rollup/derive/batches.go @@ -26,6 +26,9 @@ const ( BatchUndecided // BatchFuture indicates that the batch may be valid, but cannot be processed yet and should be checked again later BatchFuture + // BatchPast indicates that the batch is from the past, i.e. its timestamp is smaller or equal + // to the safe head's timestamp. + BatchPast ) // CheckBatch checks if the given batch can be applied on top of the given l2SafeHead, given the contextual L1 blocks the batch was included in. @@ -69,11 +72,18 @@ func checkSingularBatch(cfg *rollup.Config, log log.Logger, l1Blocks []eth.L1Blo nextTimestamp := l2SafeHead.Time + cfg.BlockTime if batch.Timestamp > nextTimestamp { + if cfg.IsHolocene(l1InclusionBlock.Time) { + log.Warn("dropping future batch", "next_timestamp", nextTimestamp) + return BatchDrop + } log.Trace("received out-of-order batch for future processing after next batch", "next_timestamp", nextTimestamp) return BatchFuture } if batch.Timestamp < nextTimestamp { - log.Warn("dropping batch with old timestamp", "min_timestamp", nextTimestamp) + log.Warn("dropping past batch with old timestamp", "min_timestamp", nextTimestamp) + if cfg.IsHolocene(l1InclusionBlock.Time) { + return BatchPast + } return BatchDrop } @@ -166,17 +176,19 @@ func checkSingularBatch(cfg *rollup.Config, log log.Logger, l1Blocks []eth.L1Blo return BatchAccept } -// checkSpanBatch implements SpanBatch validation rule. -func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1Blocks []eth.L1BlockRef, l2SafeHead eth.L2BlockRef, +// checkSpanBatchPrefix performs the span batch prefix rules for Holocene. +// Next to the validity, it also returns the parent L2 block as determined during the checks for +// further consumption. +func checkSpanBatchPrefix(ctx context.Context, cfg *rollup.Config, log log.Logger, l1Blocks []eth.L1BlockRef, l2SafeHead eth.L2BlockRef, batch *SpanBatch, l1InclusionBlock eth.L1BlockRef, l2Fetcher SafeBlockFetcher, -) BatchValidity { +) (BatchValidity, eth.L2BlockRef) { // add details to the log log = batch.LogContext(log) // sanity check we have consistent inputs if len(l1Blocks) == 0 { log.Warn("missing L1 block input, cannot proceed with batch checking") - return BatchUndecided + return BatchUndecided, eth.L2BlockRef{} } epoch := l1Blocks[0] @@ -185,64 +197,70 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B if startEpochNum == batchOrigin.Number+1 { if len(l1Blocks) < 2 { log.Info("eager batch wants to advance epoch, but could not without more L1 blocks", "current_epoch", epoch.ID()) - return BatchUndecided + return BatchUndecided, eth.L2BlockRef{} } batchOrigin = l1Blocks[1] } if !cfg.IsDelta(batchOrigin.Time) { log.Warn("received SpanBatch with L1 origin before Delta hard fork", "l1_origin", batchOrigin.ID(), "l1_origin_time", batchOrigin.Time) - return BatchDrop + return BatchDrop, eth.L2BlockRef{} } nextTimestamp := l2SafeHead.Time + cfg.BlockTime if batch.GetTimestamp() > nextTimestamp { + if cfg.IsHolocene(l1InclusionBlock.Time) { + log.Warn("dropping future span batch", "next_timestamp", nextTimestamp) + return BatchDrop, eth.L2BlockRef{} + } log.Trace("received out-of-order batch for future processing after next batch", "next_timestamp", nextTimestamp) - return BatchFuture + return BatchFuture, eth.L2BlockRef{} } if batch.GetBlockTimestamp(batch.GetBlockCount()-1) < nextTimestamp { log.Warn("span batch has no new blocks after safe head") - return BatchDrop + if cfg.IsHolocene(l1InclusionBlock.Time) { + return BatchPast, eth.L2BlockRef{} + } + return BatchDrop, eth.L2BlockRef{} } // finding parent block of the span batch. // if the span batch does not overlap the current safe chain, parentBLock should be l2SafeHead. - parentNum := l2SafeHead.Number parentBlock := l2SafeHead if batch.GetTimestamp() < nextTimestamp { if batch.GetTimestamp() > l2SafeHead.Time { // batch timestamp cannot be between safe head and next timestamp log.Warn("batch has misaligned timestamp, block time is too short") - return BatchDrop + return BatchDrop, eth.L2BlockRef{} } if (l2SafeHead.Time-batch.GetTimestamp())%cfg.BlockTime != 0 { log.Warn("batch has misaligned timestamp, not overlapped exactly") - return BatchDrop + return BatchDrop, eth.L2BlockRef{} } - parentNum = l2SafeHead.Number - (l2SafeHead.Time-batch.GetTimestamp())/cfg.BlockTime - 1 + parentNum := l2SafeHead.Number - (l2SafeHead.Time-batch.GetTimestamp())/cfg.BlockTime - 1 var err error parentBlock, err = l2Fetcher.L2BlockRefByNumber(ctx, parentNum) if err != nil { log.Warn("failed to fetch L2 block", "number", parentNum, "err", err) // unable to validate the batch for now. retry later. - return BatchUndecided + return BatchUndecided, eth.L2BlockRef{} } } if !batch.CheckParentHash(parentBlock.Hash) { log.Warn("ignoring batch with mismatching parent hash", "parent_block", parentBlock.Hash) - return BatchDrop + return BatchDrop, parentBlock } // Filter out batches that were included too late. if startEpochNum+cfg.SeqWindowSize < l1InclusionBlock.Number { log.Warn("batch was included too late, sequence window expired") - return BatchDrop + return BatchDrop, parentBlock } // Check the L1 origin of the batch if startEpochNum > parentBlock.L1Origin.Number+1 { log.Warn("batch is for future epoch too far ahead, while it has the next timestamp, so it must be invalid", "current_epoch", epoch.ID()) - return BatchDrop + return BatchDrop, parentBlock } endEpochNum := batch.GetBlockEpochNum(batch.GetBlockCount() - 1) @@ -252,7 +270,7 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B if l1Block.Number == endEpochNum { if !batch.CheckOriginHash(l1Block.Hash) { log.Warn("batch is for different L1 chain, epoch hash does not match", "expected", l1Block.Hash) - return BatchDrop + return BatchDrop, parentBlock } originChecked = true break @@ -260,13 +278,26 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B } if !originChecked { log.Info("need more l1 blocks to check entire origins of span batch") - return BatchUndecided + return BatchUndecided, parentBlock } if startEpochNum < parentBlock.L1Origin.Number { log.Warn("dropped batch, epoch is too old", "minimum", parentBlock.ID()) - return BatchDrop + return BatchDrop, parentBlock } + return BatchAccept, parentBlock +} + +// checkSpanBatch performs the full SpanBatch validation rules. +func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1Blocks []eth.L1BlockRef, l2SafeHead eth.L2BlockRef, + batch *SpanBatch, l1InclusionBlock eth.L1BlockRef, l2Fetcher SafeBlockFetcher, +) BatchValidity { + prefixValidity, parentBlock := checkSpanBatchPrefix(ctx, cfg, log, l1Blocks, l2SafeHead, batch, l1InclusionBlock, l2Fetcher) + if prefixValidity != BatchAccept { + return prefixValidity + } + + startEpochNum := uint64(batch.GetStartEpochNum()) originIdx := 0 originAdvanced := startEpochNum == parentBlock.L1Origin.Number+1 @@ -334,6 +365,8 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B } } + parentNum := parentBlock.Number + nextTimestamp := l2SafeHead.Time + cfg.BlockTime // Check overlapped blocks if batch.GetTimestamp() < nextTimestamp { for i := uint64(0); i < l2SafeHead.Number-parentNum; i++ { diff --git a/op-node/rollup/derive/batches_test.go b/op-node/rollup/derive/batches_test.go index 125fc0f02e2cf..a50773c71d754 100644 --- a/op-node/rollup/derive/batches_test.go +++ b/op-node/rollup/derive/batches_test.go @@ -43,15 +43,16 @@ func deltaAt(t *uint64) func(*rollup.Config) { func fjordAt(t *uint64) func(*rollup.Config) { return func(c *rollup.Config) { + c.DeltaTime = &zero64 c.FjordTime = t } } -func multiMod[T any](mods ...func(T)) func(T) { - return func(x T) { - for _, mod := range mods { - mod(x) - } +func holoceneAt(t *uint64) func(*rollup.Config) { + return func(c *rollup.Config) { + c.DeltaTime = &zero64 + c.FjordTime = &zero64 + c.HoloceneTime = t } } @@ -263,6 +264,23 @@ func TestValidBatch(t *testing.T) { }, Expected: BatchFuture, }, + { + Name: "future timestamp with Holocene at L1 inc", + L1Blocks: []eth.L1BlockRef{l1A, l1B, l1C}, + L2SafeHead: l2A0, + Batch: BatchWithL1InclusionBlock{ + L1InclusionBlock: l1B, + Batch: &SingularBatch{ + ParentHash: l2A1.ParentHash, + EpochNum: rollup.Epoch(l2A1.L1Origin.Number), + EpochHash: l2A1.L1Origin.Hash, + Timestamp: l2A1.Time + 1, // 1 too high + }, + }, + Expected: BatchDrop, + ExpectedLog: "dropping future batch", + ConfigMod: holoceneAt(&l1B.Time), + }, { Name: "old timestamp", L1Blocks: []eth.L1BlockRef{l1A, l1B, l1C}, @@ -279,6 +297,23 @@ func TestValidBatch(t *testing.T) { }, Expected: BatchDrop, }, + { + Name: "past timestamp with Holocene at L1 inc", + L1Blocks: []eth.L1BlockRef{l1A, l1B, l1C}, + L2SafeHead: l2A0, + Batch: BatchWithL1InclusionBlock{ + L1InclusionBlock: l1B, + Batch: &SingularBatch{ + ParentHash: l2A1.ParentHash, + EpochNum: rollup.Epoch(l2A1.L1Origin.Number), + EpochHash: l2A1.L1Origin.Hash, + Timestamp: l2A0.Time, // repeating the same time + }, + }, + Expected: BatchPast, + ExpectedLog: "dropping past batch with old timestamp", + ConfigMod: holoceneAt(&l1B.Time), + }, { Name: "misaligned timestamp", L1Blocks: []eth.L1BlockRef{l1A, l1B, l1C}, @@ -636,6 +671,26 @@ func TestValidBatch(t *testing.T) { ExpectedLog: "received out-of-order batch for future processing after next batch", ConfigMod: deltaAtGenesis, }, + { + Name: "future timestamp with Holocene at L1 inc", + L1Blocks: []eth.L1BlockRef{l1A, l1B, l1C}, + L2SafeHead: l2A0, + Batch: BatchWithL1InclusionBlock{ + L1InclusionBlock: l1B, + Batch: initializedSpanBatch([]*SingularBatch{ + { + ParentHash: l2A1.ParentHash, + EpochNum: rollup.Epoch(l2A1.L1Origin.Number), + EpochHash: l2A1.L1Origin.Hash, + Timestamp: l2A1.Time + 1, // 1 too high + Transactions: nil, + }, + }, uint64(0), big.NewInt(0)), + }, + Expected: BatchDrop, + ExpectedLog: "dropping future span batch", + ConfigMod: holoceneAt(&l1B.Time), + }, { Name: "misaligned timestamp", L1Blocks: []eth.L1BlockRef{l1A, l1B, l1C}, @@ -873,7 +928,7 @@ func TestValidBatch(t *testing.T) { }, uint64(0), big.NewInt(0)), }, Expected: BatchAccept, - ConfigMod: multiMod(deltaAtGenesis, fjordAt(&l1A.Time)), + ConfigMod: fjordAt(&l1A.Time), }, { Name: "sequencer time drift on same epoch with non-empty txs - long span", @@ -1277,6 +1332,33 @@ func TestValidBatch(t *testing.T) { ExpectedLog: "span batch has no new blocks after safe head", ConfigMod: deltaAtGenesis, }, + { + Name: "fully overlapping batch with Holocene", + L1Blocks: []eth.L1BlockRef{l1A, l1B}, + L2SafeHead: l2A2, + Batch: BatchWithL1InclusionBlock{ + L1InclusionBlock: l1B, + Batch: initializedSpanBatch([]*SingularBatch{ + { + ParentHash: l2A0.Hash, + EpochNum: rollup.Epoch(l2A1.L1Origin.Number), + EpochHash: l2A1.L1Origin.Hash, + Timestamp: l2A1.Time, + Transactions: nil, + }, + { + ParentHash: l2A1.Hash, + EpochNum: rollup.Epoch(l2A2.L1Origin.Number), + EpochHash: l2A2.L1Origin.Hash, + Timestamp: l2A2.Time, + Transactions: nil, + }, + }, uint64(0), big.NewInt(0)), + }, + Expected: BatchPast, + ExpectedLog: "span batch has no new blocks after safe head", + ConfigMod: holoceneAt(&l1B.Time), + }, { Name: "overlapping batch with invalid parent hash", L1Blocks: []eth.L1BlockRef{l1A, l1B}, diff --git a/op-node/rollup/derive/blob_data_source.go b/op-node/rollup/derive/blob_data_source.go index 2c4626941b8b5..11bb2a55ccb01 100644 --- a/op-node/rollup/derive/blob_data_source.go +++ b/op-node/rollup/derive/blob_data_source.go @@ -27,13 +27,13 @@ type BlobDataSource struct { ref eth.L1BlockRef batcherAddr common.Address dsCfg DataSourceConfig - fetcher L1TransactionFetcher + fetcher L1Fetcher blobsFetcher L1BlobsFetcher log log.Logger } // NewBlobDataSource creates a new blob data source. -func NewBlobDataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1TransactionFetcher, blobsFetcher L1BlobsFetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter { +func NewBlobDataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1Fetcher, blobsFetcher L1BlobsFetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter { return &BlobDataSource{ ref: ref, dsCfg: dsCfg, @@ -73,6 +73,32 @@ func (ds *BlobDataSource) Next(ctx context.Context) (eth.Data, error) { return data, nil } +// getTxSucceed returns all successful txs +func getTxSucceed(ctx context.Context, useInboxContract bool, fetcher L1Fetcher, hash common.Hash, txs types.Transactions) (successTxs types.Transactions, err error) { + if !useInboxContract { + // if !useInboxContract, all txs are considered successful + return txs, nil + } + _, receipts, err := fetcher.FetchReceipts(ctx, hash) + if err != nil { + return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err)) + } + + txSucceeded := make(map[common.Hash]bool) + for _, receipt := range receipts { + if receipt.Status == types.ReceiptStatusSuccessful { + txSucceeded[receipt.TxHash] = true + } + } + successTxs = make(types.Transactions, 0) + for _, tx := range txs { + if _, ok := txSucceeded[tx.Hash()]; ok { + successTxs = append(successTxs, tx) + } + } + return successTxs, nil +} + // open fetches and returns the blob or calldata (as appropriate) from all valid batcher // transactions in the referenced block. Returns an empty (non-nil) array if no batcher // transactions are found. It returns ResetError if it cannot find the referenced block or a @@ -85,6 +111,10 @@ func (ds *BlobDataSource) open(ctx context.Context) ([]blobOrCalldata, error) { } return nil, NewTemporaryError(fmt.Errorf("failed to open blob data source: %w", err)) } + txs, err = getTxSucceed(ctx, ds.dsCfg.useInboxContract, ds.fetcher, ds.ref.Hash, txs) + if err != nil { + return nil, err + } data, hashes := dataAndHashesFromTxs(txs, &ds.dsCfg, ds.batcherAddr, ds.log) @@ -120,8 +150,8 @@ func dataAndHashesFromTxs(txs types.Transactions, config *DataSourceConfig, batc var hashes []eth.IndexedBlobHash blobIndex := 0 // index of each blob in the block's blob sidecar for _, tx := range txs { - // skip any non-batcher transactions - if !isValidBatchTx(tx, config.l1Signer, config.batchInboxAddress, batcherAddr, logger) { + // skip any non-batcher transactions or failed transactions + if !(isValidBatchTx(tx, config.l1Signer, config.batchInboxAddress, batcherAddr, logger)) { blobIndex += len(tx.BlobHashes()) continue } diff --git a/op-node/rollup/derive/calldata_source.go b/op-node/rollup/derive/calldata_source.go index 0e8147261e93e..776428dce37b8 100644 --- a/op-node/rollup/derive/calldata_source.go +++ b/op-node/rollup/derive/calldata_source.go @@ -24,7 +24,7 @@ type CalldataSource struct { // Required to re-attempt fetching ref eth.L1BlockRef dsCfg DataSourceConfig - fetcher L1TransactionFetcher + fetcher L1Fetcher log log.Logger batcherAddr common.Address @@ -32,7 +32,7 @@ type CalldataSource struct { // NewCalldataSource creates a new calldata source. It suppresses errors in fetching the L1 block if they occur. // If there is an error, it will attempt to fetch the result on the next call to `Next`. -func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1TransactionFetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter { +func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1Fetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter { _, txs, err := fetcher.InfoAndTxsByHash(ctx, ref.Hash) if err != nil { return &CalldataSource{ @@ -44,6 +44,17 @@ func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConf batcherAddr: batcherAddr, } } + txs, err = getTxSucceed(ctx, dsCfg.useInboxContract, fetcher, ref.Hash, txs) + if err != nil { + return &CalldataSource{ + open: false, + ref: ref, + dsCfg: dsCfg, + fetcher: fetcher, + log: log, + batcherAddr: batcherAddr, + } + } return &CalldataSource{ open: true, data: DataFromEVMTransactions(dsCfg, batcherAddr, txs, log.New("origin", ref)), @@ -56,6 +67,10 @@ func NewCalldataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConf func (ds *CalldataSource) Next(ctx context.Context) (eth.Data, error) { if !ds.open { if _, txs, err := ds.fetcher.InfoAndTxsByHash(ctx, ds.ref.Hash); err == nil { + txs, err := getTxSucceed(ctx, ds.dsCfg.useInboxContract, ds.fetcher, ds.ref.Hash, txs) + if err != nil { + return nil, err + } ds.open = true ds.data = DataFromEVMTransactions(ds.dsCfg, ds.batcherAddr, txs, ds.log) } else if errors.Is(err, ethereum.NotFound) { diff --git a/op-node/rollup/derive/calldata_source_test.go b/op-node/rollup/derive/calldata_source_test.go index 01b2616cca3fa..31555996ddbe3 100644 --- a/op-node/rollup/derive/calldata_source_test.go +++ b/op-node/rollup/derive/calldata_source_test.go @@ -121,7 +121,7 @@ func TestDataFromEVMTransactions(t *testing.T) { } } - out := DataFromEVMTransactions(DataSourceConfig{cfg.L1Signer(), cfg.BatchInboxAddress, false}, batcherAddr, txs, testlog.Logger(t, log.LevelCrit)) + out := DataFromEVMTransactions(DataSourceConfig{cfg.L1Signer(), cfg.BatchInboxAddress, false, false}, batcherAddr, txs, testlog.Logger(t, log.LevelCrit)) require.ElementsMatch(t, expectedData, out) } diff --git a/op-node/rollup/derive/channel.go b/op-node/rollup/derive/channel.go index 48a9f585c4aa3..eec618b97e560 100644 --- a/op-node/rollup/derive/channel.go +++ b/op-node/rollup/derive/channel.go @@ -18,13 +18,15 @@ const ( ) // A Channel is a set of batches that are split into at least one, but possibly multiple frames. -// Frames are allowed to be ingested out of order. +// Frames are allowed to be ingested out of order, unless the channel is set to follow Holocene +// rules. // Each frame is ingested one by one. Once a frame with `closed` is added to the channel, the // channel may mark itself as ready for reading once all intervening frames have been added type Channel struct { // id of the channel - id ChannelID - openBlock eth.L1BlockRef + id ChannelID + openBlock eth.L1BlockRef + requireInOrder bool // estimated memory size, used to drop the channel if we have too much data size uint64 @@ -45,11 +47,14 @@ type Channel struct { highestL1InclusionBlock eth.L1BlockRef } -func NewChannel(id ChannelID, openBlock eth.L1BlockRef) *Channel { +// NewChannel creates a new channel with the given id and openening block. If requireInOrder is +// true, frames must be added in order. +func NewChannel(id ChannelID, openBlock eth.L1BlockRef, requireInOrder bool) *Channel { return &Channel{ - id: id, - inputs: make(map[uint64]Frame), - openBlock: openBlock, + id: id, + inputs: make(map[uint64]Frame), + openBlock: openBlock, + requireInOrder: requireInOrder, } } @@ -70,18 +75,22 @@ func (ch *Channel) AddFrame(frame Frame, l1InclusionBlock eth.L1BlockRef) error if ch.closed && frame.FrameNumber >= ch.endFrameNumber { return fmt.Errorf("frame number (%d) is greater than or equal to end frame number (%d) of a closed channel", frame.FrameNumber, ch.endFrameNumber) } + if ch.requireInOrder && int(frame.FrameNumber) != len(ch.inputs) { + return fmt.Errorf("frame out of order, expected %d, got %d", len(ch.inputs), frame.FrameNumber) + } // Guaranteed to succeed. Now update internal state if frame.IsLast { ch.endFrameNumber = frame.FrameNumber ch.closed = true } - // Prune frames with a number higher than the closing frame number when we receive a closing frame + // Prune frames with a number higher than the closing frame number when we receive a closing frame. + // Note that the following condition is guaranteed to never be true with strict Holocene ordering. if frame.IsLast && ch.endFrameNumber < ch.highestFrameNumber { // Do a linear scan over saved inputs instead of ranging over ID numbers - for id, prunedFrame := range ch.inputs { - if id >= uint64(ch.endFrameNumber) { - delete(ch.inputs, id) + for idx, prunedFrame := range ch.inputs { + if idx >= uint64(ch.endFrameNumber) { + delete(ch.inputs, idx) } ch.size -= frameSize(prunedFrame) } @@ -119,6 +128,10 @@ func (ch *Channel) Size() uint64 { return ch.size } +func (ch *Channel) ID() ChannelID { + return ch.id +} + // IsReady returns true iff the channel is ready to be read. func (ch *Channel) IsReady() bool { // Must see the last frame before the channel is ready to be read diff --git a/op-node/rollup/derive/channel_assembler.go b/op-node/rollup/derive/channel_assembler.go new file mode 100644 index 0000000000000..be821ca955f40 --- /dev/null +++ b/op-node/rollup/derive/channel_assembler.go @@ -0,0 +1,137 @@ +package derive + +import ( + "context" + "errors" + "io" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/log" +) + +// ChannelAssembler assembles frames into a raw channel. It replaces the ChannelBank since Holocene. +type ChannelAssembler struct { + log log.Logger + spec ChannelStageSpec + metrics Metrics + + channel *Channel + + prev NextFrameProvider +} + +var _ RawChannelProvider = (*ChannelAssembler)(nil) + +type ChannelStageSpec interface { + ChannelTimeout(t uint64) uint64 + MaxRLPBytesPerChannel(t uint64) uint64 +} + +// NewChannelAssembler creates the Holocene channel stage. +// It must only be used for derivation from Holocene origins. +func NewChannelAssembler(log log.Logger, spec ChannelStageSpec, prev NextFrameProvider, m Metrics) *ChannelAssembler { + return &ChannelAssembler{ + log: log, + spec: spec, + metrics: m, + prev: prev, + } +} + +func (ca *ChannelAssembler) Log() log.Logger { + return ca.log.New("stage", "channel", "origin", ca.Origin()) +} + +func (ca *ChannelAssembler) Origin() eth.L1BlockRef { + return ca.prev.Origin() +} + +func (ca *ChannelAssembler) Reset(context.Context, eth.L1BlockRef, eth.SystemConfig) error { + ca.resetChannel() + return io.EOF +} + +func (ca *ChannelAssembler) FlushChannel() { + ca.resetChannel() +} + +func (ca *ChannelAssembler) resetChannel() { + ca.channel = nil +} + +// Returns whether the current staging channel is timed out. Panics if there's no current channel. +func (ca *ChannelAssembler) channelTimedOut() bool { + return ca.channel.OpenBlockNumber()+ca.spec.ChannelTimeout(ca.Origin().Time) < ca.Origin().Number +} + +func (ca *ChannelAssembler) NextRawChannel(ctx context.Context) ([]byte, error) { + if ca.channel != nil && ca.channelTimedOut() { + ca.metrics.RecordChannelTimedOut() + ca.resetChannel() + } + + lgr := ca.Log() + origin := ca.Origin() + + // Note that if the current channel was already completed, we would have forwarded its data + // already. So we start by reading in frames. + if ca.channel != nil && ca.channel.IsReady() { + return nil, NewCriticalError(errors.New("unexpected ready channel")) + } + + // Ingest frames until we either hit an error (including io.EOF and NotEnoughData) or complete a + // channel. + // Note that we ingest the frame queue in a loop instead of returning NotEnoughData after a + // single frame ingestion, because it is guaranteed that the total size of new frames ingested + // per L1 origin block is limited by the size of batcher transactions in that block and it + // doesn't make a difference in computational effort if these are many small frames or one large + // frame of that size. Plus, this is really just moving data around, no decompression etc. yet. + for { + frame, err := ca.prev.NextFrame(ctx) + if err != nil { // includes io.EOF; a last frame broke the loop already + return nil, err + } + + // first frames always start a new channel, discarding an existing one + if frame.FrameNumber == 0 { + ca.metrics.RecordHeadChannelOpened() + ca.channel = NewChannel(frame.ID, origin, true) + } + if frame.FrameNumber > 0 && ca.channel == nil { + lgr.Warn("dropping non-first frame without channel", + "frame_channel", frame.ID, "frame_number", frame.FrameNumber) + continue // read more frames + } + + // Catches Holocene ordering rules. Note that even though the frame queue is guaranteed to + // only hold ordered frames in the current queue, it cannot guarantee this w.r.t. frames + // that already got dequeued. So ordering has to be checked here again. + if err := ca.channel.AddFrame(frame, origin); err != nil { + lgr.Warn("failed to add frame to channel", + "channel", ca.channel.ID(), "frame_channel", frame.ID, + "frame_number", frame.FrameNumber, "err", err) + continue // read more frames + } + if ca.channel.Size() > ca.spec.MaxRLPBytesPerChannel(ca.Origin().Time) { + lgr.Warn("dropping oversized channel", + "channel", ca.channel.ID(), "frame_number", frame.FrameNumber) + ca.resetChannel() + continue // read more frames + } + ca.metrics.RecordFrame() + + if frame.IsLast { + break // forward current complete channel + } + } + + ch := ca.channel + // Note that if we exit the frame ingestion loop, we're guaranteed to have a ready channel. + if ch == nil || !ch.IsReady() { + return nil, NewCriticalError(errors.New("unexpected non-ready channel")) + } + + ca.resetChannel() + r := ch.Reader() + return io.ReadAll(r) +} diff --git a/op-node/rollup/derive/channel_assembler_test.go b/op-node/rollup/derive/channel_assembler_test.go new file mode 100644 index 0000000000000..75429e5bae587 --- /dev/null +++ b/op-node/rollup/derive/channel_assembler_test.go @@ -0,0 +1,169 @@ +package derive + +import ( + "context" + "io" + "log/slog" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-node/metrics" + "github.com/ethereum-optimism/optimism/op-node/rollup" + rolluptest "github.com/ethereum-optimism/optimism/op-node/rollup/test" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestChannelStage_NextData(t *testing.T) { + for _, tc := range []struct { + desc string + frames [][]testFrame + expErr []error + expData []string + expChID []string + rlpOverride *uint64 + }{ + { + desc: "simple", + frames: [][]testFrame{ + {"a:0:first!"}, + }, + expErr: []error{nil}, + expData: []string{"first"}, + expChID: []string{""}, + }, + { + desc: "simple-two", + frames: [][]testFrame{ + {"a:0:first", "a:1:second!"}, + }, + expErr: []error{nil}, + expData: []string{"firstsecond"}, + expChID: []string{""}, + }, + { + desc: "drop-other", + frames: [][]testFrame{ + {"a:0:first", "b:1:foo"}, + {"a:1:second", "c:1:bar!"}, + {"a:2:third!"}, + }, + expErr: []error{io.EOF, io.EOF, nil}, + expData: []string{"", "", "firstsecondthird"}, + expChID: []string{"a", "a", ""}, + }, + { + desc: "drop-non-first", + frames: [][]testFrame{ + {"a:1:foo"}, + }, + expErr: []error{io.EOF}, + expData: []string{""}, + expChID: []string{""}, + }, + { + desc: "first-discards", + frames: [][]testFrame{ + {"b:0:foo"}, + {"a:0:first!"}, + }, + expErr: []error{io.EOF, nil}, + expData: []string{"", "first"}, + expChID: []string{"b", ""}, + }, + { + desc: "already-closed", + frames: [][]testFrame{ + {"a:0:foo"}, + {"a:1:bar!", "a:2:baz!"}, + }, + expErr: []error{io.EOF, nil}, + expData: []string{"", "foobar"}, + expChID: []string{"a", ""}, + }, + { + desc: "max-size", + frames: [][]testFrame{ + {"a:0:0123456789!"}, + }, + expErr: []error{nil}, + expData: []string{"0123456789"}, + expChID: []string{""}, + rlpOverride: ptr[uint64](frameOverhead + 10), + }, + { + desc: "oversized", + frames: [][]testFrame{ + {"a:0:0123456789x!"}, + }, + expErr: []error{io.EOF}, + expData: []string{""}, + expChID: []string{""}, + rlpOverride: ptr[uint64](frameOverhead + 10), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + fq := &fakeChannelBankInput{} + lgr := testlog.Logger(t, slog.LevelWarn) + spec := &rolluptest.ChainSpec{ + ChainSpec: rollup.NewChainSpec(&rollup.Config{}), + + MaxRLPBytesPerChannelOverride: tc.rlpOverride, + } + cs := NewChannelAssembler(lgr, spec, fq, metrics.NoopMetrics) + + for i, fs := range tc.frames { + fq.AddFrames(fs...) + data, err := cs.NextRawChannel(context.Background()) + require.Equal(t, tc.expData[i], string(data)) + require.ErrorIs(t, tc.expErr[i], err) + // invariant: never holds a ready channel + require.True(t, cs.channel == nil || !cs.channel.IsReady()) + + cid := tc.expChID[i] + if cid == "" { + require.Nil(t, cs.channel) + } else { + require.Equal(t, strChannelID(cid), cs.channel.ID()) + } + } + + // final call should always be io.EOF after exhausting frame queue + data, err := cs.NextRawChannel(context.Background()) + require.Nil(t, data) + require.Equal(t, io.EOF, err) + }) + } +} + +func TestChannelStage_NextData_Timeout(t *testing.T) { + require := require.New(t) + fq := &fakeChannelBankInput{} + lgr := testlog.Logger(t, slog.LevelWarn) + spec := rollup.NewChainSpec(&rollup.Config{GraniteTime: ptr(uint64(0))}) // const channel timeout + cs := NewChannelAssembler(lgr, spec, fq, metrics.NoopMetrics) + + fq.AddFrames("a:0:foo") + data, err := cs.NextRawChannel(context.Background()) + require.Nil(data) + require.Equal(io.EOF, err) + require.NotNil(cs.channel) + require.Equal(strChannelID("a"), cs.channel.ID()) + + // move close to timeout + fq.origin.Number = spec.ChannelTimeout(0) + fq.AddFrames("a:1:bar") + data, err = cs.NextRawChannel(context.Background()) + require.Nil(data) + require.Equal(io.EOF, err) + require.NotNil(cs.channel) + require.Equal(strChannelID("a"), cs.channel.ID()) + + // timeout channel by moving origin past timeout + fq.origin.Number = spec.ChannelTimeout(0) + 1 + fq.AddFrames("a:2:baz!") + data, err = cs.NextRawChannel(context.Background()) + require.Nil(data) + require.Equal(io.EOF, err) + require.Nil(cs.channel) +} diff --git a/op-node/rollup/derive/channel_bank.go b/op-node/rollup/derive/channel_bank.go index 8dd689dfadaae..57ab70096507d 100644 --- a/op-node/rollup/derive/channel_bank.go +++ b/op-node/rollup/derive/channel_bank.go @@ -40,13 +40,13 @@ type ChannelBank struct { prev NextFrameProvider } -var _ ResettableStage = (*ChannelBank)(nil) +var _ RawChannelProvider = (*ChannelBank)(nil) // NewChannelBank creates a ChannelBank, which should be Reset(origin) before use. -func NewChannelBank(log log.Logger, cfg *rollup.Config, prev NextFrameProvider, m Metrics) *ChannelBank { +func NewChannelBank(log log.Logger, spec *rollup.ChainSpec, prev NextFrameProvider, m Metrics) *ChannelBank { return &ChannelBank{ log: log, - spec: rollup.NewChainSpec(cfg), + spec: spec, metrics: m, channels: make(map[ChannelID]*Channel), channelQueue: make([]ChannelID, 0, 10), @@ -89,7 +89,7 @@ func (cb *ChannelBank) IngestFrame(f Frame) { cb.metrics.RecordHeadChannelOpened() } // create new channel if it doesn't exist yet - currentCh = NewChannel(f.ID, origin) + currentCh = NewChannel(f.ID, origin, false) cb.channels[f.ID] = currentCh cb.channelQueue = append(cb.channelQueue, f.ID) log.Info("created new channel") @@ -170,12 +170,12 @@ func (cb *ChannelBank) tryReadChannelAtIndex(i int) (data []byte, err error) { return data, nil } -// NextData pulls the next piece of data from the channel bank. +// NextRawChannel pulls the next piece of data from the channel bank. // Note that it attempts to pull data out of the channel bank prior to // loading data in (unlike most other stages). This is to ensure maintain // consistency around channel bank pruning which depends upon the order // of operations. -func (cb *ChannelBank) NextData(ctx context.Context) ([]byte, error) { +func (cb *ChannelBank) NextRawChannel(ctx context.Context) ([]byte, error) { // Do the read from the channel bank first data, err := cb.Read() if err == io.EOF { @@ -203,6 +203,12 @@ func (cb *ChannelBank) Reset(ctx context.Context, base eth.L1BlockRef, _ eth.Sys return io.EOF } +func (bq *ChannelBank) FlushChannel() { + // We need to implement the ChannelFlusher interface with the ChannelBank but it's never called + // of which the ChannelMux takes care. + panic("ChannelBank: invalid FlushChannel call") +} + type L1BlockRefByHashFetcher interface { L1BlockRefByHash(context.Context, common.Hash) (eth.L1BlockRef, error) } diff --git a/op-node/rollup/derive/channel_bank_test.go b/op-node/rollup/derive/channel_bank_test.go index 33763c23c5e01..7b0baddf2373c 100644 --- a/op-node/rollup/derive/channel_bank_test.go +++ b/op-node/rollup/derive/channel_bank_test.go @@ -30,6 +30,9 @@ func (f *fakeChannelBankInput) Origin() eth.L1BlockRef { } func (f *fakeChannelBankInput) NextFrame(_ context.Context) (Frame, error) { + if len(f.data) == 0 { + return Frame{}, io.EOF + } out := f.data[0] f.data = f.data[1:] return out.frame, out.err @@ -58,8 +61,12 @@ type testFrame string func (tf testFrame) ChannelID() ChannelID { parts := strings.Split(string(tf), ":") + return strChannelID(parts[0]) +} + +func strChannelID(s string) ChannelID { var chID ChannelID - copy(chID[:], parts[0]) + copy(chID[:], s) return chID } @@ -98,34 +105,32 @@ func TestChannelBankSimple(t *testing.T) { input := &fakeChannelBankInput{origin: a} input.AddFrames("a:0:first", "a:2:third!") input.AddFrames("a:1:second") - input.AddFrame(Frame{}, io.EOF) - - cfg := &rollup.Config{ChannelTimeoutBedrock: 10} - cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), cfg, input, metrics.NoopMetrics) + spec := rollup.NewChainSpec(&rollup.Config{ChannelTimeoutBedrock: 10}) + cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), spec, input, metrics.NoopMetrics) // Load the first frame - out, err := cb.NextData(context.Background()) + out, err := cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load the third frame - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load the second frame - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Pull out the channel data - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, err) require.Equal(t, "firstsecondthird", string(out)) // No more data - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, out) require.Equal(t, io.EOF, err) } @@ -142,54 +147,52 @@ func TestChannelBankInterleavedPreCanyon(t *testing.T) { input.AddFrames("b:1:deux", "a:2:third!") input.AddFrames("b:0:premiere") input.AddFrames("a:1:second") - input.AddFrame(Frame{}, io.EOF) - cfg := &rollup.Config{ChannelTimeoutBedrock: 10, CanyonTime: nil} - - cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), cfg, input, metrics.NoopMetrics) + spec := rollup.NewChainSpec(&rollup.Config{ChannelTimeoutBedrock: 10}) + cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), spec, input, metrics.NoopMetrics) // Load a:0 - out, err := cb.NextData(context.Background()) + out, err := cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load b:2 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load b:1 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load a:2 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load b:0 & Channel b is complete, but channel a was opened first - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load a:1 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Pull out the channel a - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, err) require.Equal(t, "firstsecondthird", string(out)) // Pull out the channel b - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, err) require.Equal(t, "premieredeuxtrois", string(out)) // No more data - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, out) require.Equal(t, io.EOF, err) } @@ -206,55 +209,53 @@ func TestChannelBankInterleaved(t *testing.T) { input.AddFrames("b:1:deux", "a:2:third!") input.AddFrames("b:0:premiere") input.AddFrames("a:1:second") - input.AddFrame(Frame{}, io.EOF) ct := uint64(0) - cfg := &rollup.Config{ChannelTimeoutBedrock: 10, CanyonTime: &ct} - - cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), cfg, input, metrics.NoopMetrics) + spec := rollup.NewChainSpec(&rollup.Config{ChannelTimeoutBedrock: 10, CanyonTime: &ct}) + cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), spec, input, metrics.NoopMetrics) // Load a:0 - out, err := cb.NextData(context.Background()) + out, err := cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load b:2 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load b:1 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load a:2 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load b:0 & Channel b is complete. Channel a was opened first but isn't ready - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Pull out the channel b because it's ready first. - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, err) require.Equal(t, "premieredeuxtrois", string(out)) // Load a:1 - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Pull out the channel a - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, err) require.Equal(t, "firstsecondthird", string(out)) // No more data - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, out) require.Equal(t, io.EOF, err) } @@ -267,42 +268,40 @@ func TestChannelBankDuplicates(t *testing.T) { input.AddFrames("a:0:first", "a:2:third!") input.AddFrames("a:0:altfirst", "a:2:altthird!") input.AddFrames("a:1:second") - input.AddFrame(Frame{}, io.EOF) - - cfg := &rollup.Config{ChannelTimeoutBedrock: 10} - cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), cfg, input, metrics.NoopMetrics) + spec := rollup.NewChainSpec(&rollup.Config{ChannelTimeoutBedrock: 10}) + cb := NewChannelBank(testlog.Logger(t, log.LevelCrit), spec, input, metrics.NoopMetrics) // Load the first frame - out, err := cb.NextData(context.Background()) + out, err := cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load the third frame - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load the duplicate frames - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Load the second frame - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.ErrorIs(t, err, NotEnoughData) require.Equal(t, []byte(nil), out) // Pull out the channel data. Expect to see the original set & not the duplicates - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, err) require.Equal(t, "firstsecondthird", string(out)) // No more data - out, err = cb.NextData(context.Background()) + out, err = cb.NextRawChannel(context.Background()) require.Nil(t, out) require.Equal(t, io.EOF, err) } diff --git a/op-node/rollup/derive/channel_in_reader.go b/op-node/rollup/derive/channel_in_reader.go index f7dde867bc9da..cdb648c3b870b 100644 --- a/op-node/rollup/derive/channel_in_reader.go +++ b/op-node/rollup/derive/channel_in_reader.go @@ -21,14 +21,24 @@ type ChannelInReader struct { spec *rollup.ChainSpec cfg *rollup.Config nextBatchFn func() (*BatchData, error) - prev *ChannelBank + prev RawChannelProvider metrics Metrics } -var _ ResettableStage = (*ChannelInReader)(nil) +var ( + _ ResettableStage = (*ChannelInReader)(nil) + _ ChannelFlusher = (*ChannelInReader)(nil) +) + +type RawChannelProvider interface { + ResettableStage + ChannelFlusher + Origin() eth.L1BlockRef + NextRawChannel(ctx context.Context) ([]byte, error) +} // NewChannelInReader creates a ChannelInReader, which should be Reset(origin) before use. -func NewChannelInReader(cfg *rollup.Config, log log.Logger, prev *ChannelBank, metrics Metrics) *ChannelInReader { +func NewChannelInReader(cfg *rollup.Config, log log.Logger, prev RawChannelProvider, metrics Metrics) *ChannelInReader { return &ChannelInReader{ spec: rollup.NewChainSpec(cfg), cfg: cfg, @@ -65,7 +75,7 @@ func (cr *ChannelInReader) NextChannel() { // It will return a temporary error if it needs to be called again to advance some internal state. func (cr *ChannelInReader) NextBatch(ctx context.Context) (Batch, error) { if cr.nextBatchFn == nil { - if data, err := cr.prev.NextData(ctx); err == io.EOF { + if data, err := cr.prev.NextRawChannel(ctx); err == io.EOF { return nil, io.EOF } else if err != nil { return nil, err @@ -122,3 +132,8 @@ func (cr *ChannelInReader) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.Sy cr.nextBatchFn = nil return io.EOF } + +func (cr *ChannelInReader) FlushChannel() { + cr.nextBatchFn = nil + cr.prev.FlushChannel() +} diff --git a/op-node/rollup/derive/channel_mux.go b/op-node/rollup/derive/channel_mux.go new file mode 100644 index 0000000000000..cb5ce66baffed --- /dev/null +++ b/op-node/rollup/derive/channel_mux.go @@ -0,0 +1,74 @@ +package derive + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/log" +) + +// ChannelMux multiplexes between different channel stages. +// Stages are swapped on demand during Reset calls, or explicitly with Transform. +// It currently chooses the ChannelBank pre-Holocene and the ChannelAssembler post-Holocene. +type ChannelMux struct { + log log.Logger + spec *rollup.ChainSpec + prev NextFrameProvider + m Metrics + + // embedded active stage + RawChannelProvider +} + +var _ RawChannelProvider = (*ChannelMux)(nil) + +// NewChannelMux returns a ChannelMux with the ChannelBank as activated stage. Reset has to be called before +// calling other methods, to activate the right stage for a given L1 origin. +func NewChannelMux(log log.Logger, spec *rollup.ChainSpec, prev NextFrameProvider, m Metrics) *ChannelMux { + return &ChannelMux{ + log: log, + spec: spec, + prev: prev, + m: m, + } +} + +func (c *ChannelMux) Reset(ctx context.Context, base eth.L1BlockRef, sysCfg eth.SystemConfig) error { + // TODO(12490): change to a switch over c.cfg.ActiveFork(base.Time) + switch { + default: + if _, ok := c.RawChannelProvider.(*ChannelBank); !ok { + c.log.Info("ChannelMux: activating pre-Holocene stage during reset", "origin", base) + c.RawChannelProvider = NewChannelBank(c.log, c.spec, c.prev, c.m) + } + case c.spec.IsHolocene(base.Time): + if _, ok := c.RawChannelProvider.(*ChannelAssembler); !ok { + c.log.Info("ChannelMux: activating Holocene stage during reset", "origin", base) + c.RawChannelProvider = NewChannelAssembler(c.log, c.spec, c.prev, c.m) + } + } + return c.RawChannelProvider.Reset(ctx, base, sysCfg) +} + +func (c *ChannelMux) Transform(f rollup.ForkName) { + switch f { + case rollup.Holocene: + c.TransformHolocene() + } +} + +func (c *ChannelMux) TransformHolocene() { + switch cp := c.RawChannelProvider.(type) { + case *ChannelBank: + c.log.Info("ChannelMux: transforming to Holocene stage") + c.RawChannelProvider = NewChannelAssembler(c.log, c.spec, c.prev, c.m) + case *ChannelAssembler: + // Even if the pipeline is Reset to the activation block, the previous origin will be the + // same, so transfromStages isn't called. + panic(fmt.Sprintf("Holocene ChannelAssembler already active, old origin: %v", cp.Origin())) + default: + panic(fmt.Sprintf("unknown channel stage type: %T", cp)) + } +} diff --git a/op-node/rollup/derive/channel_mux_test.go b/op-node/rollup/derive/channel_mux_test.go new file mode 100644 index 0000000000000..59fd669922ae7 --- /dev/null +++ b/op-node/rollup/derive/channel_mux_test.go @@ -0,0 +1,69 @@ +package derive + +import ( + "context" + "io" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-node/metrics" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestChannelMux_LaterHolocene(t *testing.T) { + log := testlog.Logger(t, log.LevelTrace) + ctx := context.Background() + l1A := eth.L1BlockRef{Time: 0, Hash: common.Hash{0xaa}} + l1B := eth.L1BlockRef{Time: 12, Hash: common.Hash{0xbb}} + cfg := &rollup.Config{ + HoloceneTime: &l1B.Time, + } + spec := rollup.NewChainSpec(cfg) + m := metrics.NoopMetrics + c := NewChannelMux(log, spec, nil, m) + + require.Nil(t, c.RawChannelProvider) + + err := c.Reset(ctx, l1A, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(ChannelBank), c.RawChannelProvider) + + c.Transform(rollup.Holocene) + require.IsType(t, new(ChannelAssembler), c.RawChannelProvider) + + err = c.Reset(ctx, l1B, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(ChannelAssembler), c.RawChannelProvider) + + err = c.Reset(ctx, l1A, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(ChannelBank), c.RawChannelProvider) +} + +func TestChannelMux_ActiveHolocene(t *testing.T) { + log := testlog.Logger(t, log.LevelTrace) + ctx := context.Background() + l1A := eth.L1BlockRef{Time: 42, Hash: common.Hash{0xaa}} + cfg := &rollup.Config{ + HoloceneTime: &l1A.Time, + } + spec := rollup.NewChainSpec(cfg) + // without the fake input, the panic check later would panic because of the Origin() call + prev := &fakeChannelBankInput{} + m := metrics.NoopMetrics + c := NewChannelMux(log, spec, prev, m) + + require.Nil(t, c.RawChannelProvider) + + err := c.Reset(ctx, l1A, eth.SystemConfig{}) + require.Equal(t, io.EOF, err) + require.IsType(t, new(ChannelAssembler), c.RawChannelProvider) + + require.Panics(t, func() { c.Transform(rollup.Holocene) }) +} diff --git a/op-node/rollup/derive/channel_out_test.go b/op-node/rollup/derive/channel_out_test.go index af7fe311bf1e8..328be3b7c5ad8 100644 --- a/op-node/rollup/derive/channel_out_test.go +++ b/op-node/rollup/derive/channel_out_test.go @@ -450,7 +450,7 @@ func testSpanChannelOut_MaxBlocksPerSpanBatch(t *testing.T, tt maxBlocksTest) { require.NoError(t, frame.UnmarshalBinary(&frameBuf)) require.True(t, frame.IsLast) spec := rollup.NewChainSpec(&rollupCfg) - ch := NewChannel(frame.ID, l1Origin) + ch := NewChannel(frame.ID, l1Origin, false) require.False(t, ch.IsReady()) require.NoError(t, ch.AddFrame(frame, l1Origin)) require.True(t, ch.IsReady()) diff --git a/op-node/rollup/derive/channel_test.go b/op-node/rollup/derive/channel_test.go index a6594492837b0..034e847b5d344 100644 --- a/op-node/rollup/derive/channel_test.go +++ b/op-node/rollup/derive/channel_test.go @@ -18,12 +18,13 @@ type frameValidityTC struct { frames []Frame shouldErr []bool sizes []uint64 + holocene bool } func (tc *frameValidityTC) Run(t *testing.T) { id := [16]byte{0xff} block := eth.L1BlockRef{} - ch := NewChannel(id, block) + ch := NewChannel(id, block, tc.holocene) if len(tc.frames) != len(tc.shouldErr) || len(tc.frames) != len(tc.sizes) { t.Errorf("lengths should be the same. frames: %d, shouldErr: %d, sizes: %d", len(tc.frames), len(tc.shouldErr), len(tc.sizes)) @@ -32,9 +33,9 @@ func (tc *frameValidityTC) Run(t *testing.T) { for i, frame := range tc.frames { err := ch.AddFrame(frame, block) if tc.shouldErr[i] { - require.NotNil(t, err) + require.Error(t, err) } else { - require.Nil(t, err) + require.NoError(t, err) } require.Equal(t, tc.sizes[i], ch.Size()) } @@ -105,6 +106,36 @@ func TestFrameValidity(t *testing.T) { shouldErr: []bool{false, false}, sizes: []uint64{207, 411}, }, + { + name: "holocene non first", + holocene: true, + frames: []Frame{ + {ID: id, FrameNumber: 2, Data: []byte("four")}, + }, + shouldErr: []bool{true}, + sizes: []uint64{0}, + }, + { + name: "holocene out of order", + holocene: true, + frames: []Frame{ + {ID: id, FrameNumber: 0, Data: []byte("four")}, + {ID: id, FrameNumber: 2, Data: []byte("seven__")}, + }, + shouldErr: []bool{false, true}, + sizes: []uint64{204, 204}, + }, + { + name: "holocene in order", + holocene: true, + frames: []Frame{ + {ID: id, FrameNumber: 0, Data: []byte("four")}, + {ID: id, FrameNumber: 1, Data: []byte("seven__")}, + {ID: id, FrameNumber: 2, IsLast: true, Data: []byte("2_")}, + }, + shouldErr: []bool{false, false, false}, + sizes: []uint64{204, 411, 613}, + }, } for _, tc := range testCases { diff --git a/op-node/rollup/derive/data_source.go b/op-node/rollup/derive/data_source.go index 8d064a7cdb8ca..0b23c7c18e63e 100644 --- a/op-node/rollup/derive/data_source.go +++ b/op-node/rollup/derive/data_source.go @@ -52,6 +52,7 @@ func NewDataSourceFactory(log log.Logger, cfg *rollup.Config, fetcher L1Fetcher, l1Signer: cfg.L1Signer(), batchInboxAddress: cfg.BatchInboxAddress, altDAEnabled: cfg.AltDAEnabled(), + useInboxContract: cfg.UseInboxContract(), } return &DataSourceFactory{ log: log, @@ -88,6 +89,7 @@ type DataSourceConfig struct { l1Signer types.Signer batchInboxAddress common.Address altDAEnabled bool + useInboxContract bool } // isValidBatchTx returns true if: diff --git a/op-node/rollup/derive/deriver.go b/op-node/rollup/derive/deriver.go index 760891648524c..d95fdf3017f7c 100644 --- a/op-node/rollup/derive/deriver.go +++ b/op-node/rollup/derive/deriver.go @@ -3,6 +3,7 @@ package derive import ( "context" "errors" + "fmt" "io" "github.com/ethereum-optimism/optimism/op-node/rollup" @@ -20,6 +21,7 @@ func (d DeriverIdleEvent) String() string { type DeriverL1StatusEvent struct { Origin eth.L1BlockRef + LastL2 eth.L2BlockRef } func (d DeriverL1StatusEvent) String() string { @@ -63,6 +65,18 @@ func (ev PipelineStepEvent) String() string { return "pipeline-step" } +// DepositsOnlyPayloadAttributesRequestEvent requests a deposits-only version of the attributes from +// the pipeline. It is sent by the engine deriver and received by the PipelineDeriver. +// This event got introduced with Holocene. +type DepositsOnlyPayloadAttributesRequestEvent struct { + Parent eth.BlockID + DerivedFrom eth.L1BlockRef +} + +func (ev DepositsOnlyPayloadAttributesRequestEvent) String() string { + return "deposits-only-payload-attributes-request" +} + type PipelineDeriver struct { pipeline *DerivationPipeline @@ -99,7 +113,7 @@ func (d *PipelineDeriver) OnEvent(ev event.Event) bool { attrib, err := d.pipeline.Step(d.ctx, x.PendingSafe) postOrigin := d.pipeline.Origin() if preOrigin != postOrigin { - d.emitter.Emit(DeriverL1StatusEvent{Origin: postOrigin}) + d.emitter.Emit(DeriverL1StatusEvent{Origin: postOrigin, LastL2: x.PendingSafe}) } if err == io.EOF { d.pipeline.log.Debug("Derivation process went idle", "progress", d.pipeline.Origin(), "err", err) @@ -121,8 +135,7 @@ func (d *PipelineDeriver) OnEvent(ev event.Event) bool { d.emitter.Emit(rollup.EngineTemporaryErrorEvent{Err: err}) } else { if attrib != nil { - d.needAttributesConfirmation = true - d.emitter.Emit(DerivedAttributesEvent{Attributes: attrib}) + d.emitDerivedAttributesEvent(attrib) } else { d.emitter.Emit(DeriverMoreEvent{}) // continue with the next step if we can } @@ -131,8 +144,21 @@ func (d *PipelineDeriver) OnEvent(ev event.Event) bool { d.pipeline.ConfirmEngineReset() case ConfirmReceivedAttributesEvent: d.needAttributesConfirmation = false + case DepositsOnlyPayloadAttributesRequestEvent: + d.pipeline.log.Warn("Deriving deposits-only attributes", "origin", d.pipeline.Origin()) + attrib, err := d.pipeline.DepositsOnlyAttributes(x.Parent, x.DerivedFrom) + if err != nil { + d.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("deriving deposits-only attributes: %w", err)}) + return true + } + d.emitDerivedAttributesEvent(attrib) default: return false } return true } + +func (d *PipelineDeriver) emitDerivedAttributesEvent(attrib *AttributesWithParent) { + d.needAttributesConfirmation = true + d.emitter.Emit(DerivedAttributesEvent{Attributes: attrib}) +} diff --git a/op-node/rollup/derive/doc.go b/op-node/rollup/derive/doc.go index 387508c77e0d9..fce2d9f1ce0d5 100644 --- a/op-node/rollup/derive/doc.go +++ b/op-node/rollup/derive/doc.go @@ -2,7 +2,7 @@ // and turn it into L2 blocks and results. Certain L2 data is also able to // turned back into L1 data. // -// The flow is data is as follows +// The data flow is as follows: // receipts, batches -> eth.PayloadAttributes, by parsing the L1 data and deriving L2 inputs // l2.PayloadAttributes -> l2.ExecutionPayload, by running the EVM (using an Execution Engine) // L2 block -> Corresponding L1 block info, by parsing the first deposited transaction diff --git a/op-node/rollup/derive/frame_queue.go b/op-node/rollup/derive/frame_queue.go index 77a2703290ce3..361f1cfda886b 100644 --- a/op-node/rollup/derive/frame_queue.go +++ b/op-node/rollup/derive/frame_queue.go @@ -10,7 +10,10 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" ) -var _ NextFrameProvider = &FrameQueue{} +var ( + _ NextFrameProvider = (*FrameQueue)(nil) + _ ForkTransformer = (*FrameQueue)(nil) +) //go:generate mockery --name NextDataProvider --case snake type NextDataProvider interface { @@ -33,13 +36,20 @@ func NewFrameQueue(log log.Logger, cfg *rollup.Config, prev NextDataProvider) *F } } +func (fq *FrameQueue) Transform(f rollup.ForkName) { + switch f { + case rollup.Holocene: + fq.log.Info("FrameQueue: resetting with Holocene activation") + // With Holocene activation, the frame queue is simply reset + fq.reset() + } +} + func (fq *FrameQueue) Origin() eth.L1BlockRef { return fq.prev.Origin() } func (fq *FrameQueue) NextFrame(ctx context.Context) (Frame, error) { - // TODO(12157): reset frame queue once at Holocene L1 origin block - // Only load more frames if necessary if len(fq.frames) == 0 { if err := fq.loadNextFrames(ctx); err != nil { @@ -129,7 +139,11 @@ func pruneFrameQueue(frames []Frame) []Frame { return frames } -func (fq *FrameQueue) Reset(_ context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error { - fq.frames = fq.frames[:0] +func (fq *FrameQueue) Reset(context.Context, eth.L1BlockRef, eth.SystemConfig) error { + fq.reset() return io.EOF } + +func (fq *FrameQueue) reset() { + fq.frames = fq.frames[:0] +} diff --git a/op-node/rollup/derive/payload_util.go b/op-node/rollup/derive/payload_util.go index 06a3a5a7f754b..6da1a952b5fe1 100644 --- a/op-node/rollup/derive/payload_util.go +++ b/op-node/rollup/derive/payload_util.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "fmt" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum-optimism/optimism/op-node/rollup" @@ -59,34 +60,42 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo rollupCfg.Genesis.L2.Number, payload.BlockHash, rollupCfg.Genesis.L2.Hash) } return rollupCfg.Genesis.SystemConfig, nil - } else { - if len(payload.Transactions) == 0 { - return eth.SystemConfig{}, fmt.Errorf("l2 block is missing L1 info deposit tx, block hash: %s", payload.BlockHash) - } - var tx types.Transaction - if err := tx.UnmarshalBinary(payload.Transactions[0]); err != nil { - return eth.SystemConfig{}, fmt.Errorf("failed to decode first tx to read l1 info from: %w", err) - } - if tx.Type() != types.DepositTxType { - return eth.SystemConfig{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type()) - } - info, err := L1BlockInfoFromBytes(rollupCfg, uint64(payload.Timestamp), tx.Data()) - if err != nil { - return eth.SystemConfig{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err) - } - if isEcotoneButNotFirstBlock(rollupCfg, uint64(payload.Timestamp)) { - // Translate Ecotone values back into encoded scalar if needed. - // We do not know if it was derived from a v0 or v1 scalar, - // but v1 is fine, a 0 blob base fee has the same effect. - info.L1FeeScalar[0] = 1 - binary.BigEndian.PutUint32(info.L1FeeScalar[24:28], info.BlobBaseFeeScalar) - binary.BigEndian.PutUint32(info.L1FeeScalar[28:32], info.BaseFeeScalar) + } + + if len(payload.Transactions) == 0 { + return eth.SystemConfig{}, fmt.Errorf("l2 block is missing L1 info deposit tx, block hash: %s", payload.BlockHash) + } + var tx types.Transaction + if err := tx.UnmarshalBinary(payload.Transactions[0]); err != nil { + return eth.SystemConfig{}, fmt.Errorf("failed to decode first tx to read l1 info from: %w", err) + } + if tx.Type() != types.DepositTxType { + return eth.SystemConfig{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type()) + } + info, err := L1BlockInfoFromBytes(rollupCfg, uint64(payload.Timestamp), tx.Data()) + if err != nil { + return eth.SystemConfig{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err) + } + if isEcotoneButNotFirstBlock(rollupCfg, uint64(payload.Timestamp)) { + // Translate Ecotone values back into encoded scalar if needed. + // We do not know if it was derived from a v0 or v1 scalar, + // but v1 is fine, a 0 blob base fee has the same effect. + info.L1FeeScalar[0] = 1 + binary.BigEndian.PutUint32(info.L1FeeScalar[24:28], info.BlobBaseFeeScalar) + binary.BigEndian.PutUint32(info.L1FeeScalar[28:32], info.BaseFeeScalar) + } + r := eth.SystemConfig{ + BatcherAddr: info.BatcherAddr, + Overhead: info.L1FeeOverhead, + Scalar: info.L1FeeScalar, + GasLimit: uint64(payload.GasLimit), + } + if rollupCfg.IsHolocene(uint64(payload.Timestamp)) { + if err := eip1559.ValidateHoloceneExtraData(payload.ExtraData); err != nil { + return eth.SystemConfig{}, err } - return eth.SystemConfig{ - BatcherAddr: info.BatcherAddr, - Overhead: info.L1FeeOverhead, - Scalar: info.L1FeeScalar, - GasLimit: uint64(payload.GasLimit), - }, err + d, e := eip1559.DecodeHoloceneExtraData(payload.ExtraData) + copy(r.EIP1559Params[:], eip1559.EncodeHolocene1559Params(d, e)) } + return r, nil } diff --git a/op-node/rollup/derive/pipeline.go b/op-node/rollup/derive/pipeline.go index f114e2a4b0d36..e85366fd0f0e2 100644 --- a/op-node/rollup/derive/pipeline.go +++ b/op-node/rollup/derive/pipeline.go @@ -38,6 +38,17 @@ type ResettableStage interface { Reset(ctx context.Context, base eth.L1BlockRef, baseCfg eth.SystemConfig) error } +// A ChannelFlusher flushes all internal state related to the current channel and then +// calls FlushChannel on the stage it owns. Note that this is in contrast to Reset, which +// is called by the owning Pipeline in a loop over all stages. +type ChannelFlusher interface { + FlushChannel() +} + +type ForkTransformer interface { + Transform(rollup.ForkName) +} + type L2Source interface { PayloadByHash(context.Context, common.Hash) (*eth.ExecutionPayloadEnvelope, error) PayloadByNumber(context.Context, uint64) (*eth.ExecutionPayloadEnvelope, error) @@ -79,21 +90,22 @@ type DerivationPipeline struct { func NewDerivationPipeline(log log.Logger, rollupCfg *rollup.Config, l1Fetcher L1Fetcher, l1Blobs L1BlobsFetcher, altDA AltDAInputFetcher, l2Source L2Source, metrics Metrics, ) *DerivationPipeline { + spec := rollup.NewChainSpec(rollupCfg) // Pull stages l1Traversal := NewL1Traversal(log, rollupCfg, l1Fetcher) dataSrc := NewDataSourceFactory(log, rollupCfg, l1Fetcher, l1Blobs, altDA) // auxiliary stage for L1Retrieval l1Src := NewL1Retrieval(log, dataSrc, l1Traversal) frameQueue := NewFrameQueue(log, rollupCfg, l1Src) - bank := NewChannelBank(log, rollupCfg, frameQueue, metrics) - chInReader := NewChannelInReader(rollupCfg, log, bank, metrics) - batchQueue := NewBatchQueue(log, rollupCfg, chInReader, l2Source) + channelMux := NewChannelMux(log, spec, frameQueue, metrics) + chInReader := NewChannelInReader(rollupCfg, log, channelMux, metrics) + batchMux := NewBatchMux(log, rollupCfg, chInReader, l2Source) attrBuilder := NewFetchingAttributesBuilder(rollupCfg, l1Fetcher, l2Source) - attributesQueue := NewAttributesQueue(log, rollupCfg, attrBuilder, batchQueue) + attributesQueue := NewAttributesQueue(log, rollupCfg, attrBuilder, batchMux) // Reset from ResetEngine then up from L1 Traversal. The stages do not talk to each other during // the ResetEngine, but after the ResetEngine, this is the order in which the stages could talk to each other. // Note: The ResetEngine is the only reset that can fail. - stages := []ResettableStage{l1Traversal, l1Src, altDA, frameQueue, bank, chInReader, batchQueue, attributesQueue} + stages := []ResettableStage{l1Traversal, l1Src, altDA, frameQueue, channelMux, chInReader, batchMux, attributesQueue} return &DerivationPipeline{ log: log, @@ -122,6 +134,10 @@ func (dp *DerivationPipeline) Reset() { dp.engineIsReset = false } +func (dp *DerivationPipeline) DepositsOnlyAttributes(parent eth.BlockID, derivedFrom eth.L1BlockRef) (*AttributesWithParent, error) { + return dp.attrib.DepositsOnlyAttributes(parent, derivedFrom) +} + // Origin is the L1 block of the inner-most stage of the derivation pipeline, // i.e. the L1 chain up to and including this point included and/or produced all the safe L2 blocks. func (dp *DerivationPipeline) Origin() eth.L1BlockRef { @@ -177,6 +193,7 @@ func (dp *DerivationPipeline) Step(ctx context.Context, pendingSafeHead eth.L2Bl if err := VerifyNewL1Origin(ctx, prevOrigin, dp.l1Fetcher, newOrigin); err != nil { return nil, fmt.Errorf("failed to verify L1 origin transition: %w", err) } + dp.transformStages(prevOrigin, newOrigin) dp.origin = newOrigin } @@ -238,6 +255,20 @@ func (dp *DerivationPipeline) initialReset(ctx context.Context, resetL2Safe eth. return nil } +func (db *DerivationPipeline) transformStages(oldOrigin, newOrigin eth.L1BlockRef) { + fork := db.rollupCfg.IsActivationBlock(oldOrigin.Time, newOrigin.Time) + if fork == "" { + return + } + + db.log.Info("Transforming stages", "fork", fork) + for _, stage := range db.stages { + if tf, ok := stage.(ForkTransformer); ok { + tf.Transform(fork) + } + } +} + func (dp *DerivationPipeline) ConfirmEngineReset() { dp.engineIsReset = true } diff --git a/op-node/rollup/derive/span_batch_tx.go b/op-node/rollup/derive/span_batch_tx.go index 5f5bffb02b8b6..1e971ef55aa03 100644 --- a/op-node/rollup/derive/span_batch_tx.go +++ b/op-node/rollup/derive/span_batch_tx.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" ) type spanBatchTxData interface { @@ -45,6 +46,18 @@ type spanBatchDynamicFeeTxData struct { func (txData *spanBatchDynamicFeeTxData) txType() byte { return types.DynamicFeeTxType } +type spanBatchBlobTxData struct { + Value *uint256.Int + GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *uint256.Int // a.k.a. maxFeePerGas + Data []byte + AccessList types.AccessList + BlobFeeCap *uint256.Int // a.k.a. maxFeePerBlobGas + BlobHashes []common.Hash +} + +func (txData *spanBatchBlobTxData) txType() byte { return types.BlobTxType } + // Type returns the transaction type. func (tx *spanBatchTx) Type() uint8 { return tx.inner.txType() @@ -93,6 +106,13 @@ func (tx *spanBatchTx) decodeTyped(b []byte) (spanBatchTxData, error) { return nil, fmt.Errorf("failed to decode spanBatchDynamicFeeTxData: %w", err) } return &inner, nil + case types.BlobTxType: + var inner spanBatchBlobTxData + err := rlp.DecodeBytes(b[1:], &inner) + if err != nil { + return nil, fmt.Errorf("failed to decode spanBatchBlobTxData: %w", err) + } + return &inner, nil default: return nil, types.ErrTxTypeNotSupported } @@ -168,6 +188,40 @@ func (tx *spanBatchTx) convertToFullTx(nonce, gas uint64, to *common.Address, ch R: R, S: S, } + case types.BlobTxType: + if to == nil { + return nil, fmt.Errorf("invalid blob tx: to can't be nil") + } + VU256, overflow := uint256.FromBig(V) + if overflow { + return nil, fmt.Errorf("invalid blob tx: V overflow:%v", V) + } + RU256, overflow := uint256.FromBig(R) + if overflow { + return nil, fmt.Errorf("invalid blob tx: R overflow:%v", R) + } + SU256, overflow := uint256.FromBig(S) + if overflow { + return nil, fmt.Errorf("invalid blob tx: S overflow:%v", R) + } + batchTxInner := tx.inner.(*spanBatchBlobTxData) + inner = &types.BlobTx{ + ChainID: uint256.MustFromBig(chainID), + Nonce: nonce, + GasTipCap: batchTxInner.GasTipCap, + GasFeeCap: batchTxInner.GasFeeCap, + Gas: gas, + To: *to, + Value: batchTxInner.Value, + Data: batchTxInner.Data, + AccessList: batchTxInner.AccessList, + BlobFeeCap: batchTxInner.BlobFeeCap, + BlobHashes: batchTxInner.BlobHashes, + V: VU256, + R: RU256, + S: SU256, + } + default: return nil, fmt.Errorf("invalid tx type: %d", tx.Type()) } @@ -199,6 +253,32 @@ func newSpanBatchTx(tx *types.Transaction) (*spanBatchTx, error) { Data: tx.Data(), AccessList: tx.AccessList(), } + case types.BlobTxType: + gasTipCap, overflow := uint256.FromBig(tx.GasTipCap()) + if overflow { + return nil, fmt.Errorf("tx.GasTipCap() overflow: %v", tx.GasTipCap()) + } + gasFeeCap, overflow := uint256.FromBig(tx.GasFeeCap()) + if overflow { + return nil, fmt.Errorf("tx.GasFeeCap() overflow: %v", tx.GasFeeCap()) + } + value, overflow := uint256.FromBig(tx.Value()) + if overflow { + return nil, fmt.Errorf("tx.Value() overflow: %v", tx.Value()) + } + blobFeeCap, overflow := uint256.FromBig(tx.BlobGasFeeCap()) + if overflow { + return nil, fmt.Errorf("tx.BlobGasFeeCap() overflow: %v", tx.BlobGasFeeCap()) + } + inner = &spanBatchBlobTxData{ + Value: value, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Data: tx.Data(), + AccessList: tx.AccessList(), + BlobFeeCap: blobFeeCap, + BlobHashes: tx.BlobHashes(), + } default: return nil, fmt.Errorf("invalid tx type: %d", tx.Type()) } diff --git a/op-node/rollup/derive/span_batch_txs.go b/op-node/rollup/derive/span_batch_txs.go index e4bc73d0f9874..6f7b59779cd86 100644 --- a/op-node/rollup/derive/span_batch_txs.go +++ b/op-node/rollup/derive/span_batch_txs.go @@ -271,6 +271,8 @@ func (btx *spanBatchTxs) recoverV(chainID *big.Int) error { v = bit case types.DynamicFeeTxType: v = bit + case types.BlobTxType: + v = bit default: return fmt.Errorf("invalid tx type: %d", txType) } @@ -386,6 +388,8 @@ func convertVToYParity(v uint64, txType int) (uint, error) { yParityBit = uint(v) case types.DynamicFeeTxType: yParityBit = uint(v) + case types.BlobTxType: + yParityBit = uint(v) default: return 0, fmt.Errorf("invalid tx type: %d", txType) } diff --git a/op-node/rollup/derive/span_batch_txs_test.go b/op-node/rollup/derive/span_batch_txs_test.go index 20f03899413dc..642791d6a93eb 100644 --- a/op-node/rollup/derive/span_batch_txs_test.go +++ b/op-node/rollup/derive/span_batch_txs_test.go @@ -333,6 +333,7 @@ func TestSpanBatchTxsRecoverV(t *testing.T) { chainID := big.NewInt(rng.Int63n(1000)) londonSigner := types.NewLondonSigner(chainID) + cancunSigner := types.NewCancunSigner(chainID) totalblockTxCount := 20 + rng.Intn(100) cases := []txTypeTest{ @@ -340,6 +341,7 @@ func TestSpanBatchTxsRecoverV(t *testing.T) { {"legacy tx", testutils.RandomLegacyTx, londonSigner}, {"access list tx", testutils.RandomAccessListTx, londonSigner}, {"dynamic fee tx", testutils.RandomDynamicFeeTx, londonSigner}, + {"blob tx", testutils.RandomBlobTx, cancunSigner}, } for _, testCase := range cases { diff --git a/op-node/rollup/derive/system_config.go b/op-node/rollup/derive/system_config.go index 6b0dbe50411db..72c4e713c30e9 100644 --- a/op-node/rollup/derive/system_config.go +++ b/op-node/rollup/derive/system_config.go @@ -18,9 +18,10 @@ import ( var ( SystemConfigUpdateBatcher = common.Hash{31: 0} - SystemConfigUpdateGasConfig = common.Hash{31: 1} + SystemConfigUpdateFeeScalars = common.Hash{31: 1} SystemConfigUpdateGasLimit = common.Hash{31: 2} SystemConfigUpdateUnsafeBlockSigner = common.Hash{31: 3} + SystemConfigUpdateEIP1559Params = common.Hash{31: 4} ) var ( @@ -93,7 +94,7 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L } destSysCfg.BatcherAddr = address return nil - case SystemConfigUpdateGasConfig: + case SystemConfigUpdateFeeScalars: if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 { return NewCriticalError(errors.New("invalid pointer field")) } @@ -140,6 +141,22 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L } destSysCfg.GasLimit = gasLimit return nil + case SystemConfigUpdateEIP1559Params: + if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 { + return NewCriticalError(errors.New("invalid pointer field")) + } + if length, err := solabi.ReadUint64(reader); err != nil || length != 32 { + return NewCriticalError(errors.New("invalid length field")) + } + params, err := solabi.ReadEthBytes32(reader) + if err != nil { + return NewCriticalError(errors.New("could not read eip-1559 params")) + } + if !solabi.EmptyReader(reader) { + return NewCriticalError(errors.New("too many bytes")) + } + copy(destSysCfg.EIP1559Params[:], params[24:32]) + return nil case SystemConfigUpdateUnsafeBlockSigner: // Ignored in derivation. This configurable applies to runtime configuration outside of the derivation. return nil diff --git a/op-node/rollup/derive/system_config_test.go b/op-node/rollup/derive/system_config_test.go index cab9e83be6bec..add1790938796 100644 --- a/op-node/rollup/derive/system_config_test.go +++ b/op-node/rollup/derive/system_config_test.go @@ -32,6 +32,7 @@ var ( oneUint256 = abi.Arguments{ {Type: uint256T}, } + eip1559Params = []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} ) // TestProcessSystemConfigUpdateLogEvent tests the parsing of an event and mutating the @@ -101,7 +102,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { Topics: []common.Hash{ ConfigUpdateEventABIHash, ConfigUpdateEventVersion0, - SystemConfigUpdateGasConfig, + SystemConfigUpdateFeeScalars, }, }, hook: func(t *testing.T, log *types.Log) *types.Log { @@ -151,7 +152,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { Topics: []common.Hash{ ConfigUpdateEventABIHash, ConfigUpdateEventVersion0, - SystemConfigUpdateGasConfig, + SystemConfigUpdateFeeScalars, }, }, hook: func(t *testing.T, log *types.Log) *types.Log { @@ -185,6 +186,28 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { config: eth.SystemConfig{}, err: true, }, + { + name: "SystemConfigUpdateEIP1559Params", + log: &types.Log{ + Topics: []common.Hash{ + ConfigUpdateEventABIHash, + ConfigUpdateEventVersion0, + SystemConfigUpdateEIP1559Params, + }, + }, + hook: func(t *testing.T, log *types.Log) *types.Log { + numberData, err := oneUint256.Pack(new(big.Int).SetBytes(eip1559Params)) + require.NoError(t, err) + data, err := bytesArgs.Pack(numberData) + require.NoError(t, err) + log.Data = data + return log + }, + config: eth.SystemConfig{ + EIP1559Params: eth.Bytes8(eip1559Params), + }, + err: false, + }, } for _, test := range tests { diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index 1fd751846cf3e..bb7361da3b214 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -49,6 +49,7 @@ type Metrics interface { RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) SetDerivationIdle(idle bool) + SetSequencerState(active bool) RecordL1ReorgDepth(d uint64) @@ -173,6 +174,7 @@ func NewDriver( syncCfg *sync.Config, sequencerConductor conductor.SequencerConductor, altDA AltDAIface, + dacClient engine.DACClient, ) *Driver { driverCtx, driverCancel := context.WithCancel(context.Background()) @@ -248,7 +250,7 @@ func NewDriver( findL1Origin := sequencing.NewL1OriginSelector(driverCtx, log, cfg, sequencerConfDepth) sys.Register("origin-selector", findL1Origin, opts) sequencer = sequencing.NewSequencer(driverCtx, log, cfg, attrBuilder, findL1Origin, - sequencerStateListener, sequencerConductor, asyncGossiper, metrics) + sequencerStateListener, sequencerConductor, asyncGossiper, metrics, dacClient) sys.Register("sequencer", sequencer, opts) } else { sequencer = sequencing.DisabledSequencer{} diff --git a/op-node/rollup/engine/build_cancel.go b/op-node/rollup/engine/build_cancel.go index 7c9995c28e854..4215e2d6c7f82 100644 --- a/op-node/rollup/engine/build_cancel.go +++ b/op-node/rollup/engine/build_cancel.go @@ -2,6 +2,9 @@ package engine import ( "context" + "errors" + + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -23,7 +26,9 @@ func (eq *EngDeriver) onBuildCancel(ev BuildCancelEvent) { eq.log.Warn("cancelling old block building job", "info", ev.Info) _, err := eq.ec.engine.GetPayload(ctx, ev.Info) if err != nil { - if x, ok := err.(eth.InputError); ok && x.Code == eth.UnknownPayload { //nolint:all + var rpcErr rpc.Error + if errors.As(err, &rpcErr) && eth.ErrorCode(rpcErr.ErrorCode()) == eth.UnknownPayload { + eq.log.Warn("tried cancelling unknown block building job", "info", ev.Info, "err", err) return // if unknown, then it did not need to be cancelled anymore. } eq.log.Error("failed to cancel block building job", "info", ev.Info, "err", err) diff --git a/op-node/rollup/engine/build_invalid.go b/op-node/rollup/engine/build_invalid.go index e684f2de2eade..4e58b50b105d4 100644 --- a/op-node/rollup/engine/build_invalid.go +++ b/op-node/rollup/engine/build_invalid.go @@ -3,10 +3,9 @@ package engine import ( "fmt" - "github.com/ethereum/go-ethereum/core/types" - "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" ) // BuildInvalidEvent is an internal engine event, to post-process upon invalid attributes. @@ -33,20 +32,19 @@ func (ev InvalidPayloadAttributesEvent) String() string { func (eq *EngDeriver) onBuildInvalid(ev BuildInvalidEvent) { eq.log.Warn("could not process payload attributes", "err", ev.Err) - // Count the number of deposits to see if the tx list is deposit only. - depositCount := 0 - for _, tx := range ev.Attributes.Attributes.Transactions { - if len(tx) > 0 && tx[0] == types.DepositTxType { - depositCount += 1 - } - } // Deposit transaction execution errors are suppressed in the execution engine, but if the // block is somehow invalid, there is nothing we can do to recover & we should exit. - if len(ev.Attributes.Attributes.Transactions) == depositCount { + if ev.Attributes.Attributes.IsDepositsOnly() { eq.log.Error("deposit only block was invalid", "parent", ev.Attributes.Parent, "err", ev.Err) eq.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("failed to process block with only deposit transactions: %w", ev.Err)}) return } + + if ev.Attributes.IsDerived() && eq.cfg.IsHolocene(ev.Attributes.DerivedFrom.Time) { + eq.emitDepositsOnlyPayloadAttributesRequest(ev.Attributes.Parent.ID(), ev.Attributes.DerivedFrom) + return + } + // Revert the pending safe head to the safe head. eq.ec.SetPendingSafeL2Head(eq.ec.SafeL2Head()) // suppress the error b/c we want to retry with the next batch from the batch queue @@ -61,3 +59,12 @@ func (eq *EngDeriver) onBuildInvalid(ev BuildInvalidEvent) { // Signal that we deemed the attributes as unfit eq.emitter.Emit(InvalidPayloadAttributesEvent(ev)) } + +func (eq *EngDeriver) emitDepositsOnlyPayloadAttributesRequest(parent eth.BlockID, derivedFrom eth.L1BlockRef) { + eq.log.Warn("Holocene active, requesting deposits-only attributes", "parent", parent, "derived_from", derivedFrom) + // request deposits-only version + eq.emitter.Emit(derive.DepositsOnlyPayloadAttributesRequestEvent{ + Parent: parent, + DerivedFrom: derivedFrom, + }) +} diff --git a/op-node/rollup/engine/build_seal.go b/op-node/rollup/engine/build_seal.go index a5c72c74fdb76..cd3ca88882713 100644 --- a/op-node/rollup/engine/build_seal.go +++ b/op-node/rollup/engine/build_seal.go @@ -2,9 +2,12 @@ package engine import ( "context" + "errors" "fmt" "time" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -14,8 +17,8 @@ type PayloadSealInvalidEvent struct { Info eth.PayloadInfo Err error - IsLastInSpan bool - DerivedFrom eth.L1BlockRef + Concluding bool + DerivedFrom eth.L1BlockRef } func (ev PayloadSealInvalidEvent) String() string { @@ -30,8 +33,8 @@ type PayloadSealExpiredErrorEvent struct { Info eth.PayloadInfo Err error - IsLastInSpan bool - DerivedFrom eth.L1BlockRef + Concluding bool + DerivedFrom eth.L1BlockRef } func (ev PayloadSealExpiredErrorEvent) String() string { @@ -42,7 +45,7 @@ type BuildSealEvent struct { Info eth.PayloadInfo BuildStarted time.Time // if payload should be promoted to safe (must also be pending safe, see DerivedFrom) - IsLastInSpan bool + Concluding bool // payload is promoted to pending-safe if non-zero DerivedFrom eth.L1BlockRef } @@ -58,7 +61,8 @@ func (eq *EngDeriver) onBuildSeal(ev BuildSealEvent) { sealingStart := time.Now() envelope, err := eq.ec.engine.GetPayload(ctx, ev.Info) if err != nil { - if x, ok := err.(eth.InputError); ok && x.Code == eth.UnknownPayload { //nolint:all + var rpcErr rpc.Error + if errors.As(err, &rpcErr) && eth.ErrorCode(rpcErr.ErrorCode()) == eth.UnknownPayload { eq.log.Warn("Cannot seal block, payload ID is unknown", "payloadID", ev.Info.ID, "payload_time", ev.Info.Timestamp, "started_time", ev.BuildStarted) @@ -69,10 +73,10 @@ func (eq *EngDeriver) onBuildSeal(ev BuildSealEvent) { // same attributes with a new block-building job from here to recover from this error. // We name it "expired", as this generally identifies a timeout, unknown job, or otherwise invalidated work. eq.emitter.Emit(PayloadSealExpiredErrorEvent{ - Info: ev.Info, - Err: fmt.Errorf("failed to seal execution payload (ID: %s): %w", ev.Info.ID, err), - IsLastInSpan: ev.IsLastInSpan, - DerivedFrom: ev.DerivedFrom, + Info: ev.Info, + Err: fmt.Errorf("failed to seal execution payload (ID: %s): %w", ev.Info.ID, err), + Concluding: ev.Concluding, + DerivedFrom: ev.DerivedFrom, }) return } @@ -82,8 +86,8 @@ func (eq *EngDeriver) onBuildSeal(ev BuildSealEvent) { Info: ev.Info, Err: fmt.Errorf("failed sanity-check of execution payload contents (ID: %s, blockhash: %s): %w", ev.Info.ID, envelope.ExecutionPayload.BlockHash, err), - IsLastInSpan: ev.IsLastInSpan, - DerivedFrom: ev.DerivedFrom, + Concluding: ev.Concluding, + DerivedFrom: ev.DerivedFrom, }) return } @@ -91,10 +95,10 @@ func (eq *EngDeriver) onBuildSeal(ev BuildSealEvent) { ref, err := derive.PayloadToBlockRef(eq.cfg, envelope.ExecutionPayload) if err != nil { eq.emitter.Emit(PayloadSealInvalidEvent{ - Info: ev.Info, - Err: fmt.Errorf("failed to decode L2 block ref from payload: %w", err), - IsLastInSpan: ev.IsLastInSpan, - DerivedFrom: ev.DerivedFrom, + Info: ev.Info, + Err: fmt.Errorf("failed to decode L2 block ref from payload: %w", err), + Concluding: ev.Concluding, + DerivedFrom: ev.DerivedFrom, }) return } @@ -106,14 +110,16 @@ func (eq *EngDeriver) onBuildSeal(ev BuildSealEvent) { eq.metrics.RecordSequencerBuildingDiffTime(buildTime - time.Duration(eq.cfg.BlockTime)*time.Second) txnCount := len(envelope.ExecutionPayload.Transactions) - eq.metrics.CountSequencedTxs(txnCount) + depositCount, _ := lastDeposit(envelope.ExecutionPayload.Transactions) + eq.metrics.CountSequencedTxsInBlock(txnCount, depositCount) - eq.log.Debug("Processed new L2 block", "l2_unsafe", ref, "l1_origin", ref.L1Origin, - "txs", txnCount, "time", ref.Time, "seal_time", sealTime, "build_time", buildTime) + eq.log.Debug("Built new L2 block", "l2_unsafe", ref, "l1_origin", ref.L1Origin, + "txs", txnCount, "deposits", depositCount, "time", ref.Time, "seal_time", sealTime, "build_time", buildTime) eq.emitter.Emit(BuildSealedEvent{ - IsLastInSpan: ev.IsLastInSpan, + Concluding: ev.Concluding, DerivedFrom: ev.DerivedFrom, + BuildStarted: ev.BuildStarted, Info: ev.Info, Envelope: envelope, Ref: ref, diff --git a/op-node/rollup/engine/build_sealed.go b/op-node/rollup/engine/build_sealed.go index d588d77b7f223..5ceff489ecc66 100644 --- a/op-node/rollup/engine/build_sealed.go +++ b/op-node/rollup/engine/build_sealed.go @@ -1,16 +1,19 @@ package engine import ( + "time" + "github.com/ethereum-optimism/optimism/op-service/eth" ) // BuildSealedEvent is emitted by the engine when a payload finished building, // but is not locally inserted as canonical block yet type BuildSealedEvent struct { - // if payload should be promoted to safe (must also be pending safe, see DerivedFrom) - IsLastInSpan bool + // if payload should be promoted to (local) safe (must also be pending safe, see DerivedFrom) + Concluding bool // payload is promoted to pending-safe if non-zero - DerivedFrom eth.L1BlockRef + DerivedFrom eth.L1BlockRef + BuildStarted time.Time Info eth.PayloadInfo Envelope *eth.ExecutionPayloadEnvelope @@ -25,10 +28,11 @@ func (eq *EngDeriver) onBuildSealed(ev BuildSealedEvent) { // If a (pending) safe block, immediately process the block if ev.DerivedFrom != (eth.L1BlockRef{}) { eq.emitter.Emit(PayloadProcessEvent{ - IsLastInSpan: ev.IsLastInSpan, + Concluding: ev.Concluding, DerivedFrom: ev.DerivedFrom, Envelope: ev.Envelope, Ref: ev.Ref, + BuildStarted: ev.BuildStarted, }) } } diff --git a/op-node/rollup/engine/build_start.go b/op-node/rollup/engine/build_start.go index 22c178d36cd0f..2c36053e1bbce 100644 --- a/op-node/rollup/engine/build_start.go +++ b/op-node/rollup/engine/build_start.go @@ -68,7 +68,7 @@ func (eq *EngDeriver) onBuildStart(ev BuildStartEvent) { eq.emitter.Emit(BuildStartedEvent{ Info: eth.PayloadInfo{ID: id, Timestamp: uint64(ev.Attributes.Attributes.Timestamp)}, BuildStarted: buildStartTime, - IsLastInSpan: ev.Attributes.IsLastInSpan, + Concluding: ev.Attributes.Concluding, DerivedFrom: ev.Attributes.DerivedFrom, Parent: ev.Attributes.Parent, }) diff --git a/op-node/rollup/engine/build_started.go b/op-node/rollup/engine/build_started.go index 78b737d214c77..1193c09fb3519 100644 --- a/op-node/rollup/engine/build_started.go +++ b/op-node/rollup/engine/build_started.go @@ -13,8 +13,8 @@ type BuildStartedEvent struct { Parent eth.L2BlockRef - // if payload should be promoted to safe (must also be pending safe, see DerivedFrom) - IsLastInSpan bool + // if payload should be promoted to (local) safe (must also be pending safe, see DerivedFrom) + Concluding bool // payload is promoted to pending-safe if non-zero DerivedFrom eth.L1BlockRef } @@ -29,7 +29,7 @@ func (eq *EngDeriver) onBuildStarted(ev BuildStartedEvent) { eq.emitter.Emit(BuildSealEvent{ Info: ev.Info, BuildStarted: ev.BuildStarted, - IsLastInSpan: ev.IsLastInSpan, + Concluding: ev.Concluding, DerivedFrom: ev.DerivedFrom, }) } diff --git a/op-node/rollup/engine/engine_controller.go b/op-node/rollup/engine/engine_controller.go index 4ac8b88a50cf4..0a36ded826dbd 100644 --- a/op-node/rollup/engine/engine_controller.go +++ b/op-node/rollup/engine/engine_controller.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" @@ -42,6 +43,10 @@ type ExecEngine interface { L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) } +type DACClient interface { + UploadBlobs(context.Context, *eth.ExecutionPayloadEnvelope) error +} + type EngineController struct { engine ExecEngine // Underlying execution engine RPC log log.Logger @@ -86,7 +91,8 @@ type EngineController struct { } func NewEngineController(engine ExecEngine, log log.Logger, metrics derive.Metrics, - rollupCfg *rollup.Config, syncCfg *sync.Config, emitter event.Emitter) *EngineController { + rollupCfg *rollup.Config, syncCfg *sync.Config, emitter event.Emitter, +) *EngineController { syncStatus := syncStatusCL if syncCfg.SyncMode == sync.ELSync { syncStatus = syncStatusWillStartEL @@ -283,11 +289,11 @@ func (e *EngineController) TryUpdateEngine(ctx context.Context) error { defer logFn() fcRes, err := e.engine.ForkchoiceUpdate(ctx, &fc, nil) if err != nil { - var inputErr eth.InputError - if errors.As(err, &inputErr) { - switch inputErr.Code { + var rpcErr rpc.Error + if errors.As(err, &rpcErr) { + switch eth.ErrorCode(rpcErr.ErrorCode()) { case eth.InvalidForkchoiceState: - return derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap())) + return derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr)) default: return derive.NewTemporaryError(fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)) } @@ -328,6 +334,7 @@ func (e *EngineController) InsertUnsafePayload(ctx context.Context, envelope *et } } // Insert the payload & then call FCU + newPayloadStart := time.Now() status, err := e.engine.NewPayload(ctx, envelope.ExecutionPayload, envelope.ParentBeaconBlockRoot) if err != nil { return derive.NewTemporaryError(fmt.Errorf("failed to update insert payload: %w", err)) @@ -340,6 +347,7 @@ func (e *EngineController) InsertUnsafePayload(ctx context.Context, envelope *et return derive.NewTemporaryError(fmt.Errorf("cannot process unsafe payload: new - %v; parent: %v; err: %w", payload.ID(), payload.ParentID(), eth.NewPayloadErr(payload, status))) } + newPayloadFinish := time.Now() // Mark the new payload as valid fc := eth.ForkchoiceState{ @@ -359,13 +367,14 @@ func (e *EngineController) InsertUnsafePayload(ctx context.Context, envelope *et } logFn := e.logSyncProgressMaybe() defer logFn() + fcu2Start := time.Now() fcRes, err := e.engine.ForkchoiceUpdate(ctx, &fc, nil) if err != nil { - var inputErr eth.InputError - if errors.As(err, &inputErr) { - switch inputErr.Code { + var rpcErr rpc.Error + if errors.As(err, &rpcErr) { + switch eth.ErrorCode(rpcErr.ErrorCode()) { case eth.InvalidForkchoiceState: - return derive.NewResetError(fmt.Errorf("pre-unsafe-block forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap())) + return derive.NewResetError(fmt.Errorf("pre-unsafe-block forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr)) default: return derive.NewTemporaryError(fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)) } @@ -378,6 +387,7 @@ func (e *EngineController) InsertUnsafePayload(ctx context.Context, envelope *et return derive.NewTemporaryError(fmt.Errorf("cannot prepare unsafe chain for new payload: new - %v; parent: %v; err: %w", payload.ID(), payload.ParentID(), eth.ForkchoiceUpdateErr(fcRes.PayloadStatus))) } + fcu2Finish := time.Now() e.SetUnsafeHead(ref) e.needFCUCall = false e.emitter.Emit(UnsafeUpdateEvent{Ref: ref}) @@ -395,6 +405,16 @@ func (e *EngineController) InsertUnsafePayload(ctx context.Context, envelope *et }) } + totalTime := fcu2Finish.Sub(newPayloadStart) + e.log.Info("Inserted new L2 unsafe block (synchronous)", + "hash", envelope.ExecutionPayload.BlockHash, + "number", uint64(envelope.ExecutionPayload.BlockNumber), + "newpayload_time", common.PrettyDuration(newPayloadFinish.Sub(newPayloadStart)), + "fcu2_time", common.PrettyDuration(fcu2Finish.Sub(fcu2Start)), + "total_time", common.PrettyDuration(totalTime), + "mgas", float64(envelope.ExecutionPayload.GasUsed)/1000000, + "mgasps", float64(envelope.ExecutionPayload.GasUsed)*1000/float64(totalTime)) + return nil } @@ -439,13 +459,16 @@ func (e *EngineController) TryBackupUnsafeReorg(ctx context.Context) (bool, erro defer logFn() fcRes, err := e.engine.ForkchoiceUpdate(ctx, &fc, nil) if err != nil { - var inputErr eth.InputError - if errors.As(err, &inputErr) { - e.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false) - switch inputErr.Code { + var rpcErr rpc.Error + if errors.As(err, &rpcErr) { + switch eth.ErrorCode(rpcErr.ErrorCode()) { case eth.InvalidForkchoiceState: - return true, derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap())) + e.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false) + return true, derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr)) default: + // Retry when forkChoiceUpdate returns non-input error. + // Do not reset backupUnsafeHead because it will be used again. + e.needFCUCallForBackupUnsafeReorg = true return true, derive.NewTemporaryError(fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)) } } else { diff --git a/op-node/rollup/engine/engine_update.go b/op-node/rollup/engine/engine_update.go index c79dbdcd0f4d0..dbf578d303768 100644 --- a/op-node/rollup/engine/engine_update.go +++ b/op-node/rollup/engine/engine_update.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" ) // isDepositTx checks an opaqueTx to determine if it is a Deposit Transaction @@ -84,15 +85,15 @@ const ( func startPayload(ctx context.Context, eng ExecEngine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes) (id eth.PayloadID, errType BlockInsertionErrType, err error) { fcRes, err := eng.ForkchoiceUpdate(ctx, &fc, attrs) if err != nil { - var inputErr eth.InputError - if errors.As(err, &inputErr) { - switch inputErr.Code { + var rpcErr rpc.Error + if errors.As(err, &rpcErr) { + switch code := eth.ErrorCode(rpcErr.ErrorCode()); code { case eth.InvalidForkchoiceState: - return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("pre-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", inputErr.Unwrap()) + return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("pre-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr) case eth.InvalidPayloadAttributes: - return eth.PayloadID{}, BlockInsertPayloadErr, fmt.Errorf("payload attributes are not valid, cannot build block: %w", inputErr.Unwrap()) + return eth.PayloadID{}, BlockInsertPayloadErr, fmt.Errorf("payload attributes are not valid, cannot build block: %w", rpcErr) default: - if inputErr.Code.IsEngineError() { + if code.IsEngineError() { return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("unexpected engine error code in forkchoice-updated response: %w", err) } else { return eth.PayloadID{}, BlockInsertTemporaryErr, fmt.Errorf("unexpected generic error code in forkchoice-updated response: %w", err) diff --git a/op-node/rollup/engine/events.go b/op-node/rollup/engine/events.go index b05410a6dcef8..fbc8b7b1db2fe 100644 --- a/op-node/rollup/engine/events.go +++ b/op-node/rollup/engine/events.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-node/rollup" @@ -15,7 +16,7 @@ import ( ) type Metrics interface { - CountSequencedTxs(count int) + CountSequencedTxsInBlock(txns int, deposits int) RecordSequencerBuildingDiffTime(duration time.Duration) RecordSequencerSealingTime(duration time.Duration) @@ -25,8 +26,7 @@ type Metrics interface { // forkchoice-update event, to signal the latest forkchoice to other derivers. // This helps decouple derivers from the actual engine state, // while also not making the derivers wait for a forkchoice update at random. -type ForkchoiceRequestEvent struct { -} +type ForkchoiceRequestEvent struct{} func (ev ForkchoiceRequestEvent) String() string { return "forkchoice-request" @@ -98,10 +98,19 @@ func (ev PendingSafeUpdateEvent) String() string { return "pending-safe-update" } +type InteropPendingSafeChangedEvent struct { + Ref eth.L2BlockRef + DerivedFrom eth.L1BlockRef +} + +func (ev InteropPendingSafeChangedEvent) String() string { + return "interop-pending-safe-changed" +} + // PromotePendingSafeEvent signals that a block can be marked as pending-safe, and/or safe. type PromotePendingSafeEvent struct { Ref eth.L2BlockRef - Safe bool + Concluding bool // Concludes the pending phase, so can be promoted to (local) safe DerivedFrom eth.L1BlockRef } @@ -175,8 +184,7 @@ func (ev ProcessAttributesEvent) String() string { return "process-attributes" } -type PendingSafeRequestEvent struct { -} +type PendingSafeRequestEvent struct{} func (ev PendingSafeRequestEvent) String() string { return "pending-safe-request" @@ -190,20 +198,73 @@ func (ev ProcessUnsafePayloadEvent) String() string { return "process-unsafe-payload" } -type TryBackupUnsafeReorgEvent struct { -} +type TryBackupUnsafeReorgEvent struct{} func (ev TryBackupUnsafeReorgEvent) String() string { return "try-backup-unsafe-reorg" } type TryUpdateEngineEvent struct { + // These fields will be zero-value (BuildStarted,InsertStarted=time.Time{}, Envelope=nil) if + // this event is emitted outside of engineDeriver.onPayloadSuccess + BuildStarted time.Time + InsertStarted time.Time + Envelope *eth.ExecutionPayloadEnvelope } func (ev TryUpdateEngineEvent) String() string { return "try-update-engine" } +// Checks for the existence of the Envelope field, which is only +// added by the PayloadSuccessEvent +func (ev TryUpdateEngineEvent) triggeredByPayloadSuccess() bool { + return ev.Envelope != nil +} + +// Returns key/value pairs that can be logged and are useful for plotting +// block build/insert time as a way to measure performance. +func (ev TryUpdateEngineEvent) getBlockProcessingMetrics() []interface{} { + fcuFinish := time.Now() + payload := ev.Envelope.ExecutionPayload + + logValues := []interface{}{ + "hash", payload.BlockHash, + "number", uint64(payload.BlockNumber), + "state_root", payload.StateRoot, + "timestamp", uint64(payload.Timestamp), + "parent", payload.ParentHash, + "prev_randao", payload.PrevRandao, + "fee_recipient", payload.FeeRecipient, + "txs", len(payload.Transactions), + } + + var totalTime time.Duration + var mgasps float64 + if !ev.BuildStarted.IsZero() { + totalTime = fcuFinish.Sub(ev.BuildStarted) + logValues = append(logValues, + "build_time", common.PrettyDuration(ev.InsertStarted.Sub(ev.BuildStarted)), + "insert_time", common.PrettyDuration(fcuFinish.Sub(ev.InsertStarted)), + ) + } else if !ev.InsertStarted.IsZero() { + totalTime = fcuFinish.Sub(ev.InsertStarted) + } + + // Avoid divide-by-zero for mgasps + if totalTime > 0 { + mgasps = float64(payload.GasUsed) * 1000 / float64(totalTime) + } + + logValues = append(logValues, + "total_time", common.PrettyDuration(totalTime), + "mgas", float64(payload.GasUsed)/1000000, + "mgasps", mgasps, + ) + + return logValues +} + type ForceEngineResetEvent struct { Unsafe, Safe, Finalized eth.L2BlockRef } @@ -268,7 +329,8 @@ type EngDeriver struct { var _ event.Deriver = (*EngDeriver)(nil) func NewEngDeriver(log log.Logger, ctx context.Context, cfg *rollup.Config, - metrics Metrics, ec *EngineController) *EngDeriver { + metrics Metrics, ec *EngineController, +) *EngDeriver { return &EngDeriver{ log: log, cfg: cfg, @@ -316,6 +378,9 @@ func (d *EngDeriver) OnEvent(ev event.Event) bool { } else { d.emitter.Emit(rollup.CriticalErrorEvent{Err: fmt.Errorf("unexpected TryUpdateEngine error type: %w", err)}) } + } else if x.triggeredByPayloadSuccess() { + logValues := x.getBlockProcessingMetrics() + d.log.Info("Inserted new L2 unsafe block", logValues...) } case ProcessUnsafePayloadEvent: ref, err := derive.PayloadToBlockRef(d.cfg, x.Envelope.ExecutionPayload) @@ -395,19 +460,26 @@ func (d *EngDeriver) OnEvent(ev event.Event) bool { // Only promote if not already stale. // Resets/overwrites happen through engine-resets, not through promotion. if x.Ref.Number > d.ec.PendingSafeL2Head().Number { + d.log.Debug("Updating pending safe", "pending_safe", x.Ref, "local_safe", d.ec.LocalSafeL2Head(), "unsafe", d.ec.UnsafeL2Head(), "concluding", x.Concluding) d.ec.SetPendingSafeL2Head(x.Ref) d.emitter.Emit(PendingSafeUpdateEvent{ PendingSafe: d.ec.PendingSafeL2Head(), Unsafe: d.ec.UnsafeL2Head(), }) } - if x.Safe && x.Ref.Number > d.ec.LocalSafeL2Head().Number { + if x.Concluding && x.Ref.Number > d.ec.LocalSafeL2Head().Number { d.emitter.Emit(PromoteLocalSafeEvent{ Ref: x.Ref, DerivedFrom: x.DerivedFrom, }) } + // TODO(#12646): temporary interop work-around, assumes Holocene local-safe progression behavior. + d.emitter.Emit(InteropPendingSafeChangedEvent{ + Ref: x.Ref, + DerivedFrom: x.DerivedFrom, + }) case PromoteLocalSafeEvent: + d.log.Debug("Updating local safe", "local_safe", x.Ref, "safe", d.ec.SafeL2Head(), "unsafe", d.ec.UnsafeL2Head()) d.ec.SetLocalSafeHead(x.Ref) d.emitter.Emit(LocalSafeUpdateEvent(x)) case LocalSafeUpdateEvent: @@ -416,6 +488,7 @@ func (d *EngDeriver) OnEvent(ev event.Event) bool { d.emitter.Emit(PromoteSafeEvent(x)) } case PromoteSafeEvent: + d.log.Debug("Updating safe", "safe", x.Ref, "unsafe", d.ec.UnsafeL2Head()) d.ec.SetSafeHead(x.Ref) // Finalizer can pick up this safe cross-block now d.emitter.Emit(SafeDerivedEvent{Safe: x.Ref, DerivedFrom: x.DerivedFrom}) @@ -457,10 +530,10 @@ func (d *EngDeriver) OnEvent(ev event.Event) bool { d.onBuildStart(x) case BuildStartedEvent: d.onBuildStarted(x) - case BuildSealedEvent: - d.onBuildSealed(x) case BuildSealEvent: d.onBuildSeal(x) + case BuildSealedEvent: + d.onBuildSealed(x) case BuildInvalidEvent: d.onBuildInvalid(x) case BuildCancelEvent: diff --git a/op-node/rollup/engine/payload_process.go b/op-node/rollup/engine/payload_process.go index 4102287f3d238..272fce3febcf6 100644 --- a/op-node/rollup/engine/payload_process.go +++ b/op-node/rollup/engine/payload_process.go @@ -3,16 +3,18 @@ package engine import ( "context" "fmt" + "time" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" ) type PayloadProcessEvent struct { - // if payload should be promoted to safe (must also be pending safe, see DerivedFrom) - IsLastInSpan bool + // if payload should be promoted to (local) safe (must also be pending safe, see DerivedFrom) + Concluding bool // payload is promoted to pending-safe if non-zero - DerivedFrom eth.L1BlockRef + DerivedFrom eth.L1BlockRef + BuildStarted time.Time Envelope *eth.ExecutionPayloadEnvelope Ref eth.L2BlockRef @@ -26,25 +28,43 @@ func (eq *EngDeriver) onPayloadProcess(ev PayloadProcessEvent) { ctx, cancel := context.WithTimeout(eq.ctx, payloadProcessTimeout) defer cancel() + insertStart := time.Now() status, err := eq.ec.engine.NewPayload(ctx, ev.Envelope.ExecutionPayload, ev.Envelope.ParentBeaconBlockRoot) if err != nil { eq.emitter.Emit(rollup.EngineTemporaryErrorEvent{ - Err: fmt.Errorf("failed to insert execution payload: %w", err)}) + Err: fmt.Errorf("failed to insert execution payload: %w", err), + }) return } switch status.Status { case eth.ExecutionInvalid, eth.ExecutionInvalidBlockHash: + // Depending on execution engine, not all block-validity checks run immediately on build-start + // at the time of the forkchoiceUpdated engine-API call, nor during getPayload. + if ev.DerivedFrom != (eth.L1BlockRef{}) && eq.cfg.IsHolocene(ev.DerivedFrom.Time) { + eq.emitDepositsOnlyPayloadAttributesRequest(ev.Ref.ParentID(), ev.DerivedFrom) + return + } + eq.emitter.Emit(PayloadInvalidEvent{ Envelope: ev.Envelope, - Err: eth.NewPayloadErr(ev.Envelope.ExecutionPayload, status)}) + Err: eth.NewPayloadErr(ev.Envelope.ExecutionPayload, status), + }) return case eth.ExecutionValid: - eq.emitter.Emit(PayloadSuccessEvent(ev)) + eq.emitter.Emit(PayloadSuccessEvent{ + Concluding: ev.Concluding, + DerivedFrom: ev.DerivedFrom, + BuildStarted: ev.BuildStarted, + InsertStarted: insertStart, + Envelope: ev.Envelope, + Ref: ev.Ref, + }) return default: eq.emitter.Emit(rollup.EngineTemporaryErrorEvent{ - Err: eth.NewPayloadErr(ev.Envelope.ExecutionPayload, status)}) + Err: eth.NewPayloadErr(ev.Envelope.ExecutionPayload, status), + }) return } } diff --git a/op-node/rollup/engine/payload_success.go b/op-node/rollup/engine/payload_success.go index 7bb4a38307e13..17d8b0163b5f5 100644 --- a/op-node/rollup/engine/payload_success.go +++ b/op-node/rollup/engine/payload_success.go @@ -1,14 +1,18 @@ package engine import ( + "time" + "github.com/ethereum-optimism/optimism/op-service/eth" ) type PayloadSuccessEvent struct { - // if payload should be promoted to safe (must also be pending safe, see DerivedFrom) - IsLastInSpan bool + // if payload should be promoted to (local) safe (must also be pending safe, see DerivedFrom) + Concluding bool // payload is promoted to pending-safe if non-zero - DerivedFrom eth.L1BlockRef + DerivedFrom eth.L1BlockRef + BuildStarted time.Time + InsertStarted time.Time Envelope *eth.ExecutionPayloadEnvelope Ref eth.L2BlockRef @@ -25,16 +29,14 @@ func (eq *EngDeriver) onPayloadSuccess(ev PayloadSuccessEvent) { if ev.DerivedFrom != (eth.L1BlockRef{}) { eq.emitter.Emit(PromotePendingSafeEvent{ Ref: ev.Ref, - Safe: ev.IsLastInSpan, + Concluding: ev.Concluding, DerivedFrom: ev.DerivedFrom, }) } - payload := ev.Envelope.ExecutionPayload - eq.log.Info("Inserted block", "hash", payload.BlockHash, "number", uint64(payload.BlockNumber), - "state_root", payload.StateRoot, "timestamp", uint64(payload.Timestamp), "parent", payload.ParentHash, - "prev_randao", payload.PrevRandao, "fee_recipient", payload.FeeRecipient, - "txs", len(payload.Transactions), "last_in_span", ev.IsLastInSpan, "derived_from", ev.DerivedFrom) - - eq.emitter.Emit(TryUpdateEngineEvent{}) + eq.emitter.Emit(TryUpdateEngineEvent{ + BuildStarted: ev.BuildStarted, + InsertStarted: ev.InsertStarted, + Envelope: ev.Envelope, + }) } diff --git a/op-node/rollup/finality/finalizer.go b/op-node/rollup/finality/finalizer.go index 046b94664e055..fb49bf7afd093 100644 --- a/op-node/rollup/finality/finalizer.go +++ b/op-node/rollup/finality/finalizer.go @@ -73,6 +73,8 @@ type Finalizer struct { ctx context.Context + cfg *rollup.Config + emitter event.Emitter // finalizedL1 is the currently perceived finalized L1 block. @@ -98,6 +100,7 @@ func NewFinalizer(ctx context.Context, log log.Logger, cfg *rollup.Config, l1Fet lookback := calcFinalityLookback(cfg) return &Finalizer{ ctx: ctx, + cfg: cfg, log: log, finalizedL1: eth.L1BlockRef{}, triedFinalizeAt: 0, @@ -253,6 +256,14 @@ func (fi *Finalizer) tryFinalize() { func (fi *Finalizer) onDerivedSafeBlock(l2Safe eth.L2BlockRef, derivedFrom eth.L1BlockRef) { fi.mu.Lock() defer fi.mu.Unlock() + + // Stop registering blocks after interop. + // Finality in interop is determined by the superchain backend, + // i.e. the op-supervisor RPC identifies which L2 block may be finalized. + if fi.cfg.IsInterop(l2Safe.Time) { + return + } + // remember the last L2 block that we fully derived from the given finality data if len(fi.finalityData) == 0 || fi.finalityData[len(fi.finalityData)-1].L1Block.Number < derivedFrom.Number { // prune finality data if necessary, before appending any data. diff --git a/op-node/rollup/finality/finalizer_test.go b/op-node/rollup/finality/finalizer_test.go index cdaf4006c0e0f..e65b21abf6236 100644 --- a/op-node/rollup/finality/finalizer_test.go +++ b/op-node/rollup/finality/finalizer_test.go @@ -472,4 +472,35 @@ func TestEngineQueue_Finalize(t *testing.T) { fi.OnEvent(TryFinalizeEvent{}) emitter.AssertExpectations(t) }) + + // The Finalizer does not promote any blocks to finalized status after interop. + // Blocks after interop are finalized with the interop deriver and interop backend. + t.Run("disable-after-interop", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + l1F := &testutils.MockL1Source{} + defer l1F.AssertExpectations(t) + l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) + l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) + + emitter := &testutils.MockEmitter{} + fi := NewFinalizer(context.Background(), logger, &rollup.Config{ + InteropTime: &refC1.Time, + }, l1F) + fi.AttachEmitter(emitter) + + // now say C0 and C1 were included in D and became the new safe head + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC0, DerivedFrom: refD}) + fi.OnEvent(engine.SafeDerivedEvent{Safe: refC1, DerivedFrom: refD}) + fi.OnEvent(derive.DeriverIdleEvent{Origin: refD}) + emitter.AssertExpectations(t) + + emitter.ExpectOnce(TryFinalizeEvent{}) + fi.OnEvent(FinalizeL1Event{FinalizedL1: refD}) + emitter.AssertExpectations(t) + + // C1 was Interop, C0 was not yet interop and can be finalized + emitter.ExpectOnce(engine.PromoteFinalizedEvent{Ref: refC0}) + fi.OnEvent(TryFinalizeEvent{}) + emitter.AssertExpectations(t) + }) } diff --git a/op-node/rollup/interop/interop.go b/op-node/rollup/interop/interop.go index 904f93d941129..94fa77a5b3092 100644 --- a/op-node/rollup/interop/interop.go +++ b/op-node/rollup/interop/interop.go @@ -3,6 +3,7 @@ package interop import ( "context" "fmt" + "strings" "sync" "time" @@ -10,10 +11,13 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-node/rollup/event" "github.com/ethereum-optimism/optimism/op-node/rollup/finality" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -24,13 +28,19 @@ type InteropBackend interface { SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) - DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) + CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (eth.L1BlockRef, error) - UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error - UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error + UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error + UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error } +// For testing usage, the backend of the supervisor implements the interface, no need for RPC. +var _ InteropBackend = (*backend.SupervisorBackend)(nil) + +// For RPC usage, the supervisor client implements the interop backend. +var _ InteropBackend = (*sources.SupervisorClient)(nil) + type L2Source interface { L2BlockRefByNumber(context.Context, uint64) (eth.L2BlockRef, error) L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error) @@ -83,10 +93,17 @@ func (d *InteropDeriver) OnEvent(ev event.Event) bool { switch x := ev.(type) { case engine.UnsafeUpdateEvent: d.onLocalUnsafeUpdate(x) - case engine.LocalSafeUpdateEvent: - d.onLocalSafeUpdate(x) + case engine.InteropPendingSafeChangedEvent: + d.onInteropPendingSafeChangedEvent(x) case finality.FinalizeL1Event: d.onFinalizedL1(x) + case derive.DeriverL1StatusEvent: + d.log.Debug("deriver L1 traversal event", "l1", x.Origin, "l2", x.LastL2) + // Register traversal of L1, repeat the last local-safe L2 + d.onInteropPendingSafeChangedEvent(engine.InteropPendingSafeChangedEvent{ + Ref: x.LastL2, + DerivedFrom: x.Origin, + }) case engine.CrossUnsafeUpdateEvent: if err := d.onCrossUnsafe(x); err != nil { d.log.Error("Failed to process cross-unsafe update", "err", err) @@ -109,7 +126,7 @@ func (d *InteropDeriver) onLocalUnsafeUpdate(x engine.UnsafeUpdateEvent) { d.log.Debug("Signaling unsafe L2 head update to interop backend", "head", x.Ref) ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout) defer cancel() - if err := d.backend.UpdateLocalUnsafe(ctx, d.chainID, x.Ref); err != nil { + if err := d.backend.UpdateLocalUnsafe(ctx, d.chainID, x.Ref.BlockRef()); err != nil { d.log.Warn("Failed to signal unsafe L2 head to interop backend", "head", x.Ref, "err", err) // still continue to try and do a cross-unsafe update } @@ -117,13 +134,16 @@ func (d *InteropDeriver) onLocalUnsafeUpdate(x engine.UnsafeUpdateEvent) { d.emitter.Emit(engine.RequestCrossUnsafeEvent{}) } -func (d *InteropDeriver) onLocalSafeUpdate(x engine.LocalSafeUpdateEvent) { +func (d *InteropDeriver) onInteropPendingSafeChangedEvent(x engine.InteropPendingSafeChangedEvent) { d.log.Debug("Signaling derived-from update to interop backend", "derivedFrom", x.DerivedFrom, "block", x.Ref) ctx, cancel := context.WithTimeout(d.driverCtx, rpcTimeout) defer cancel() - if err := d.backend.UpdateLocalSafe(ctx, d.chainID, x.DerivedFrom, x.Ref); err != nil { + if err := d.backend.UpdateLocalSafe(ctx, d.chainID, x.DerivedFrom, x.Ref.BlockRef()); err != nil { d.log.Debug("Failed to signal derived-from update to interop backend", "derivedFrom", x.DerivedFrom, "block", x.Ref) - // still continue to try and do a cross-safe update + if strings.Contains(err.Error(), "too far behind") { + d.log.Error("Supervisor is too far behind, resetting derivation", "err", err) + d.emitter.Emit(rollup.ResetEvent{Err: fmt.Errorf("supervisor is too far behind: %w", err)}) + } } // Now that the op-supervisor is aware of the new local-safe block, we want to check if cross-safe changed. d.emitter.Emit(engine.RequestCrossSafeEvent{}) @@ -212,10 +232,15 @@ func (d *InteropDeriver) onCrossSafeUpdateEvent(x engine.CrossSafeUpdateEvent) e // and then reset derivation, so this op-node can help get the supervisor back in sync. return nil } - derivedFrom, err := d.backend.DerivedFrom(ctx, d.chainID, result.Cross.Hash, result.Cross.Number) + derived := eth.BlockID{ + Hash: result.Cross.Hash, + Number: result.Cross.Number, + } + derivedFrom, err := d.backend.CrossDerivedFrom(ctx, d.chainID, derived) if err != nil { return fmt.Errorf("failed to get derived-from of %s: %w", result.Cross, err) } + d.log.Info("New cross-safe block", "block", result.Cross.Number) ref, err := d.l2.L2BlockRefByHash(ctx, result.Cross.Hash) if err != nil { return fmt.Errorf("failed to get block ref of %s: %w", result.Cross, err) @@ -252,6 +277,7 @@ func (d *InteropDeriver) onFinalizedUpdate(x engine.FinalizedUpdateEvent) error if err != nil { return fmt.Errorf("failed to get block ref of %s: %w", finalized, err) } + d.log.Info("New finalized block from supervisor", "block", finalized.Number) d.emitter.Emit(engine.PromoteFinalizedEvent{ Ref: ref, }) diff --git a/op-node/rollup/interop/interop_test.go b/op-node/rollup/interop/interop_test.go index 4078e4881a07b..d4fb87f0dd924 100644 --- a/op-node/rollup/interop/interop_test.go +++ b/op-node/rollup/interop/interop_test.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/engine" "github.com/ethereum-optimism/optimism/op-node/rollup/finality" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" supervisortypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" @@ -35,7 +36,7 @@ func TestInteropDeriver(t *testing.T) { t.Run("local-unsafe blocks push to supervisor and trigger cross-unsafe attempts", func(t *testing.T) { emitter.ExpectOnce(engine.RequestCrossUnsafeEvent{}) unsafeHead := testutils.RandomL2BlockRef(rng) - interopBackend.ExpectUpdateLocalUnsafe(chainID, unsafeHead, nil) + interopBackend.ExpectUpdateLocalUnsafe(chainID, unsafeHead.BlockRef(), nil) interopDeriver.OnEvent(engine.UnsafeUpdateEvent{Ref: unsafeHead}) emitter.AssertExpectations(t) interopBackend.AssertExpectations(t) @@ -92,8 +93,8 @@ func TestInteropDeriver(t *testing.T) { emitter.ExpectOnce(engine.RequestCrossSafeEvent{}) derivedFrom := testutils.RandomBlockRef(rng) localSafe := testutils.RandomL2BlockRef(rng) - interopBackend.ExpectUpdateLocalSafe(chainID, derivedFrom, localSafe, nil) - interopDeriver.OnEvent(engine.LocalSafeUpdateEvent{ + interopBackend.ExpectUpdateLocalSafe(chainID, derivedFrom, localSafe.BlockRef(), nil) + interopDeriver.OnEvent(engine.InteropPendingSafeChangedEvent{ Ref: localSafe, DerivedFrom: derivedFrom, }) @@ -114,7 +115,11 @@ func TestInteropDeriver(t *testing.T) { Cross: nextCrossSafe.ID(), } interopBackend.ExpectSafeView(chainID, localView, supervisorView, nil) - interopBackend.ExpectDerivedFrom(chainID, nextCrossSafe.Hash, nextCrossSafe.Number, derivedFrom, nil) + derived := eth.BlockID{ + Hash: nextCrossSafe.Hash, + Number: nextCrossSafe.Number, + } + interopBackend.ExpectDerivedFrom(chainID, derived, derivedFrom, nil) l2Source.ExpectL2BlockRefByHash(nextCrossSafe.Hash, nextCrossSafe, nil) emitter.ExpectOnce(engine.PromoteSafeEvent{ Ref: nextCrossSafe, diff --git a/op-node/rollup/sequencing/sequencer.go b/op-node/rollup/sequencing/sequencer.go index 538caafe41445..b9a8e08a852fa 100644 --- a/op-node/rollup/sequencing/sequencer.go +++ b/op-node/rollup/sequencing/sequencer.go @@ -33,6 +33,7 @@ type L1OriginSelectorIface interface { } type Metrics interface { + SetSequencerState(active bool) RecordSequencerInconsistentL1Origin(from eth.BlockID, to eth.BlockID) RecordSequencerReset() RecordSequencingError() @@ -55,8 +56,7 @@ type AsyncGossiper interface { // This event is used to prioritize sequencer work over derivation work, // by emitting it before e.g. a derivation-pipeline step. // A future sequencer in an async world may manage its own execution. -type SequencerActionEvent struct { -} +type SequencerActionEvent struct{} func (ev SequencerActionEvent) String() string { return "sequencer-action" @@ -119,6 +119,8 @@ type Sequencer struct { // toBlockRef converts a payload to a block-ref, and is only configurable for test-purposes toBlockRef func(rollupCfg *rollup.Config, payload *eth.ExecutionPayload) (eth.L2BlockRef, error) + + dacClient engine.DACClient } var _ SequencerIface = (*Sequencer)(nil) @@ -129,7 +131,9 @@ func NewSequencer(driverCtx context.Context, log log.Logger, rollupCfg *rollup.C listener SequencerStateListener, conductor conductor.SequencerConductor, asyncGossip AsyncGossiper, - metrics Metrics) *Sequencer { + metrics Metrics, + dacClient engine.DACClient, +) *Sequencer { return &Sequencer{ ctx: driverCtx, log: log, @@ -143,6 +147,7 @@ func NewSequencer(driverCtx context.Context, log log.Logger, rollupCfg *rollup.C metrics: metrics, timeNow: time.Now, toBlockRef: derive.PayloadToBlockRef, + dacClient: dacClient, } } @@ -265,12 +270,35 @@ func (d *Sequencer) onBuildSealed(x engine.BuildSealedEvent) { "txs", len(x.Envelope.ExecutionPayload.Transactions), "time", uint64(x.Envelope.ExecutionPayload.Timestamp)) + { + envelope := x.Envelope + if envelope.BlobsBundle != nil && len(envelope.BlobsBundle.Blobs) > 0 { + // Deriving is based on onchain-data which doesn't contain L2 blob. + if x.DerivedFrom != (eth.L1BlockRef{}) { + d.emitter.Emit(rollup.EngineTemporaryErrorEvent{ + Err: fmt.Errorf("got blobs when deriving")}) + return + } + if d.dacClient != nil { + ctx, cancel := context.WithTimeout(d.ctx, time.Second*5) + defer cancel() + err := d.dacClient.UploadBlobs(ctx, envelope) + if err != nil { + d.emitter.Emit(rollup.EngineTemporaryErrorEvent{ + Err: fmt.Errorf("UploadBlobs failed: %w", err)}) + return + } + } + } + } + // generous timeout, the conductor is important ctx, cancel := context.WithTimeout(d.ctx, time.Second*30) defer cancel() if err := d.conductor.CommitUnsafePayload(ctx, x.Envelope); err != nil { d.emitter.Emit(rollup.EngineTemporaryErrorEvent{ - Err: fmt.Errorf("failed to commit unsafe payload to conductor: %w", err)}) + Err: fmt.Errorf("failed to commit unsafe payload to conductor: %w", err), + }) return } @@ -280,8 +308,9 @@ func (d *Sequencer) onBuildSealed(x engine.BuildSealedEvent) { d.asyncGossip.Gossip(x.Envelope) // Now after having gossiped the block, try to put it in our own canonical chain d.emitter.Emit(engine.PayloadProcessEvent{ - IsLastInSpan: x.IsLastInSpan, + Concluding: x.Concluding, DerivedFrom: x.DerivedFrom, + BuildStarted: x.BuildStarted, Envelope: x.Envelope, Ref: x.Ref, }) @@ -333,7 +362,7 @@ func (d *Sequencer) onPayloadSuccess(x engine.PayloadSuccessEvent) { d.asyncGossip.Clear() } -func (d *Sequencer) onSequencerAction(x SequencerActionEvent) { +func (d *Sequencer) onSequencerAction(SequencerActionEvent) { d.log.Debug("Sequencer action") payload := d.asyncGossip.Get() if payload != nil { @@ -355,10 +384,10 @@ func (d *Sequencer) onSequencerAction(x SequencerActionEvent) { // meaning that we have seen BuildSealedEvent already. // We can retry processing to make it canonical. d.emitter.Emit(engine.PayloadProcessEvent{ - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, - Envelope: payload, - Ref: ref, + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, + Envelope: payload, + Ref: ref, }) d.latest.Ref = ref } else { @@ -370,7 +399,7 @@ func (d *Sequencer) onSequencerAction(x SequencerActionEvent) { d.emitter.Emit(engine.BuildSealEvent{ Info: d.latest.Info, BuildStarted: d.latest.Started, - IsLastInSpan: false, + Concluding: false, DerivedFrom: eth.L1BlockRef{}, }) } else if d.latest == (BuildingState{}) { @@ -382,8 +411,7 @@ func (d *Sequencer) onSequencerAction(x SequencerActionEvent) { func (d *Sequencer) onEngineTemporaryError(x rollup.EngineTemporaryErrorEvent) { if d.latest == (BuildingState{}) { - d.log.Debug("Engine reported temporary error, but sequencer is not using engine", "err", x.Err) - return + d.log.Debug("Engine reported temporary error while building state is empty", "err", x.Err) } d.log.Error("Engine failed temporarily, backing off sequencer", "err", x.Err) if errors.Is(x.Err, engine.ErrEngineSyncing) { // if it is syncing we can back off by more @@ -416,7 +444,7 @@ func (d *Sequencer) onReset(x rollup.ResetEvent) { d.nextActionOK = false } -func (d *Sequencer) onEngineResetConfirmedEvent(x engine.EngineResetConfirmedEvent) { +func (d *Sequencer) onEngineResetConfirmedEvent(engine.EngineResetConfirmedEvent) { d.nextActionOK = d.active.Load() // Before sequencing we can wait a block, // assuming the execution-engine just churned through some work for the reset. @@ -552,10 +580,10 @@ func (d *Sequencer) startBuildingBlock() { // Start a payload building process. withParent := &derive.AttributesWithParent{ - Attributes: attrs, - Parent: l2Head, - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, // zero, not going to be pending-safe / safe + Attributes: attrs, + Parent: l2Head, + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, // zero, not going to be pending-safe / safe } // Don't try to start building a block again, until we have heard back from this attempt @@ -619,6 +647,7 @@ func (d *Sequencer) Init(ctx context.Context, active bool) error { if active { return d.forceStart() } else { + d.metrics.SetSequencerState(false) if err := d.listener.SequencerStopped(); err != nil { return fmt.Errorf("failed to notify sequencer-state listener of initial stopped state: %w", err) } @@ -652,6 +681,7 @@ func (d *Sequencer) forceStart() error { d.nextActionOK = true d.nextAction = d.timeNow() d.active.Store(true) + d.metrics.SetSequencerState(true) d.log.Info("Sequencer has been started", "next action", d.nextAction) return nil } @@ -697,6 +727,7 @@ func (d *Sequencer) Stop(ctx context.Context) (common.Hash, error) { d.nextActionOK = false d.active.Store(false) + d.metrics.SetSequencerState(false) d.log.Info("Sequencer has been stopped") return d.latestHead.Hash, nil } diff --git a/op-node/rollup/sequencing/sequencer_chaos_test.go b/op-node/rollup/sequencing/sequencer_chaos_test.go index 5d64ab101d7cf..93ba254c1b545 100644 --- a/op-node/rollup/sequencing/sequencer_chaos_test.go +++ b/op-node/rollup/sequencing/sequencer_chaos_test.go @@ -91,7 +91,7 @@ func (c *ChaoticEngine) OnEvent(ev event.Event) bool { Info: c.currentPayloadInfo, BuildStarted: c.clock.Now(), Parent: x.Attributes.Parent, - IsLastInSpan: false, + Concluding: false, DerivedFrom: eth.L1BlockRef{}, }) } @@ -124,10 +124,10 @@ func (c *ChaoticEngine) OnEvent(ev event.Event) bool { if c.currentPayloadInfo == (eth.PayloadInfo{}) { c.emitter.Emit(engine.PayloadSealExpiredErrorEvent{ - Info: x.Info, - Err: errors.New("job was cancelled"), - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, + Info: x.Info, + Err: errors.New("job was cancelled"), + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, }) return true } @@ -142,17 +142,17 @@ func (c *ChaoticEngine) OnEvent(ev event.Event) bool { switch { case p < 0.03: // 3% c.emitter.Emit(engine.PayloadSealInvalidEvent{ - Info: x.Info, - Err: errors.New("mock invalid seal"), - IsLastInSpan: x.IsLastInSpan, - DerivedFrom: x.DerivedFrom, + Info: x.Info, + Err: errors.New("mock invalid seal"), + Concluding: x.Concluding, + DerivedFrom: x.DerivedFrom, }) case p < 0.08: // 5% c.emitter.Emit(engine.PayloadSealExpiredErrorEvent{ - Info: x.Info, - Err: errors.New("mock temp engine error"), - IsLastInSpan: x.IsLastInSpan, - DerivedFrom: x.DerivedFrom, + Info: x.Info, + Err: errors.New("mock temp engine error"), + Concluding: x.Concluding, + DerivedFrom: x.DerivedFrom, }) default: payloadEnvelope := ð.ExecutionPayloadEnvelope{ @@ -178,11 +178,11 @@ func (c *ChaoticEngine) OnEvent(ev event.Event) bool { SequenceNumber: 0, // ignored } c.emitter.Emit(engine.BuildSealedEvent{ - Info: x.Info, - Envelope: payloadEnvelope, - Ref: payloadRef, - IsLastInSpan: x.IsLastInSpan, - DerivedFrom: x.DerivedFrom, + Info: x.Info, + Envelope: payloadEnvelope, + Ref: payloadRef, + Concluding: x.Concluding, + DerivedFrom: x.DerivedFrom, }) } c.currentPayloadInfo = eth.PayloadInfo{} @@ -216,7 +216,13 @@ func (c *ChaoticEngine) OnEvent(ev event.Event) bool { c.clockRandomIncrement(0, time.Second*3) } c.unsafe = x.Ref - c.emitter.Emit(engine.PayloadSuccessEvent(x)) + c.emitter.Emit(engine.PayloadSuccessEvent{ + Concluding: x.Concluding, + DerivedFrom: x.DerivedFrom, + BuildStarted: x.BuildStarted, + Envelope: x.Envelope, + Ref: x.Ref, + }) // With event delay, the engine would update and signal the new forkchoice. c.emitter.Emit(engine.ForkchoiceRequestEvent{}) } diff --git a/op-node/rollup/sequencing/sequencer_test.go b/op-node/rollup/sequencing/sequencer_test.go index 3265711a0c469..6f43d59caa0b5 100644 --- a/op-node/rollup/sequencing/sequencer_test.go +++ b/op-node/rollup/sequencing/sequencer_test.go @@ -46,7 +46,8 @@ func decodeID(data []byte) eth.BlockID { } func (m *FakeAttributesBuilder) PreparePayloadAttributes(ctx context.Context, - l2Parent eth.L2BlockRef, epoch eth.BlockID) (attrs *eth.PayloadAttributes, err error) { + l2Parent eth.L2BlockRef, epoch eth.BlockID, +) (attrs *eth.PayloadAttributes, err error) { gasLimit := eth.Uint64Quantity(30_000_000) attrs = ð.PayloadAttributes{ Timestamp: eth.Uint64Quantity(l2Parent.Time + m.cfg.BlockTime), @@ -315,7 +316,7 @@ func TestSequencer_StaleBuild(t *testing.T) { Info: payloadInfo, BuildStarted: startedTime, Parent: head, - IsLastInSpan: false, + Concluding: false, DerivedFrom: eth.L1BlockRef{}, }) @@ -325,7 +326,7 @@ func TestSequencer_StaleBuild(t *testing.T) { emitter.ExpectOnce(engine.BuildSealEvent{ Info: payloadInfo, BuildStarted: startedTime, - IsLastInSpan: false, + Concluding: false, DerivedFrom: eth.L1BlockRef{}, }) seq.OnEvent(SequencerActionEvent{}) @@ -355,18 +356,18 @@ func TestSequencer_StaleBuild(t *testing.T) { SequenceNumber: 0, } emitter.ExpectOnce(engine.PayloadProcessEvent{ - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, - Envelope: payloadEnvelope, - Ref: payloadRef, + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, + Envelope: payloadEnvelope, + Ref: payloadRef, }) // And report back the sealing result to the engine seq.OnEvent(engine.BuildSealedEvent{ - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, - Info: payloadInfo, - Envelope: payloadEnvelope, - Ref: payloadRef, + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, + Info: payloadInfo, + Envelope: payloadEnvelope, + Ref: payloadRef, }) // The sequencer should start processing the payload emitter.AssertExpectations(t) @@ -521,7 +522,7 @@ func TestSequencerBuild(t *testing.T) { Info: payloadInfo, BuildStarted: startedTime, Parent: head, - IsLastInSpan: false, + Concluding: false, DerivedFrom: eth.L1BlockRef{}, }) // The sealing should now be scheduled as next action. @@ -535,7 +536,7 @@ func TestSequencerBuild(t *testing.T) { emitter.ExpectOnce(engine.BuildSealEvent{ Info: payloadInfo, BuildStarted: startedTime, - IsLastInSpan: false, + Concluding: false, DerivedFrom: eth.L1BlockRef{}, }) seq.OnEvent(SequencerActionEvent{}) @@ -564,18 +565,18 @@ func TestSequencerBuild(t *testing.T) { SequenceNumber: 0, } emitter.ExpectOnce(engine.PayloadProcessEvent{ - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, - Envelope: payloadEnvelope, - Ref: payloadRef, + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, + Envelope: payloadEnvelope, + Ref: payloadRef, }) // And report back the sealing result to the engine seq.OnEvent(engine.BuildSealedEvent{ - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, - Info: payloadInfo, - Envelope: payloadEnvelope, - Ref: payloadRef, + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, + Info: payloadInfo, + Envelope: payloadEnvelope, + Ref: payloadRef, }) // The sequencer should start processing the payload emitter.AssertExpectations(t) @@ -587,10 +588,10 @@ func TestSequencerBuild(t *testing.T) { // Mock that the processing was successful seq.OnEvent(engine.PayloadSuccessEvent{ - IsLastInSpan: false, - DerivedFrom: eth.L1BlockRef{}, - Envelope: payloadEnvelope, - Ref: payloadRef, + Concluding: false, + DerivedFrom: eth.L1BlockRef{}, + Envelope: payloadEnvelope, + Ref: payloadRef, }) require.Nil(t, deps.asyncGossip.payload, "async gossip should have cleared,"+ " after previous publishing and now having persisted the block ourselves") @@ -643,6 +644,8 @@ func createSequencer(log log.Logger) (*Sequencer, *sequencerTestDeps) { DeltaTime: new(uint64), EcotoneTime: new(uint64), FjordTime: new(uint64), + GraniteTime: new(uint64), + HoloceneTime: new(uint64), } deps := &sequencerTestDeps{ cfg: cfg, @@ -658,7 +661,7 @@ func createSequencer(log log.Logger) (*Sequencer, *sequencerTestDeps) { } seq := NewSequencer(context.Background(), log, cfg, deps.attribBuilder, deps.l1OriginSelector, deps.seqState, deps.conductor, - deps.asyncGossip, metrics.NoopMetrics) + deps.asyncGossip, metrics.NoopMetrics, nil) // We create mock payloads, with the epoch-id as tx[0], rather than proper L1Block-info deposit tx. seq.toBlockRef = func(rollupCfg *rollup.Config, payload *eth.ExecutionPayload) (eth.L2BlockRef, error) { return eth.L2BlockRef{ diff --git a/op-node/rollup/superchain.go b/op-node/rollup/superchain.go index 21a74323c05f7..577a8dab7ceae 100644 --- a/op-node/rollup/superchain.go +++ b/op-node/rollup/superchain.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum-optimism/superchain-registry/superchain" ) -var OPStackSupport = params.ProtocolVersionV0{Build: [8]byte{}, Major: 8, Minor: 0, Patch: 0, PreRelease: 0}.Encode() +var OPStackSupport = params.ProtocolVersionV0{Build: [8]byte{}, Major: 9, Minor: 0, Patch: 0, PreRelease: 1}.Encode() // LoadOPStackRollupConfig loads the rollup configuration of the requested chain ID from the superchain-registry. // Some chains may require a SystemConfigProvider to retrieve any values not part of the registry. @@ -91,6 +91,7 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { EcotoneTime: chConfig.EcotoneTime, FjordTime: chConfig.FjordTime, GraniteTime: chConfig.GraniteTime, + HoloceneTime: chConfig.HoloceneTime, BatchInboxAddress: common.Address(chConfig.BatchInboxAddr), DepositContractAddress: common.Address(addrs.OptimismPortalProxy), L1SystemConfigAddress: common.Address(addrs.SystemConfigProxy), diff --git a/op-node/rollup/test/chain_spec.go b/op-node/rollup/test/chain_spec.go new file mode 100644 index 0000000000000..fd527549118f2 --- /dev/null +++ b/op-node/rollup/test/chain_spec.go @@ -0,0 +1,18 @@ +package test + +import "github.com/ethereum-optimism/optimism/op-node/rollup" + +// ChainSpec wraps a *rollup.ChainSpec, allowing to optionally override individual values, +// otherwise just returning the underlying ChainSpec's values. +type ChainSpec struct { + *rollup.ChainSpec + + MaxRLPBytesPerChannelOverride *uint64 // MaxRLPBytesPerChannel override +} + +func (cs *ChainSpec) MaxRLPBytesPerChannel(t uint64) uint64 { + if o := cs.MaxRLPBytesPerChannelOverride; o != nil { + return *o + } + return cs.ChainSpec.MaxRLPBytesPerChannel(t) +} diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 0c611a5d8d35a..60e61dbd8314f 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -2,8 +2,10 @@ package rollup import ( "context" + "encoding/json" "errors" "fmt" + "io" "math/big" "time" @@ -137,6 +139,32 @@ type Config struct { // AltDAConfig. We are in the process of migrating to the AltDAConfig from these legacy top level values AltDAConfig *AltDAConfig `json:"alt_da,omitempty"` + + L2BlobConfig *L2BlobConfig `json:"l2_blob_config,omitempty"` + InboxContractConfig *InboxContractConfig `json:"inbox_contract_config,omitempty"` +} + +type L2BlobConfig struct { + L2BlobTime *uint64 `json:"l2BlobTime,omitempty"` +} + +type InboxContractConfig struct { + UseInboxContract bool `json:"use_inbox_contract,omitempty"` +} + +// IsL2Blob returns whether l2 blob is enabled +func (cfg *Config) IsL2Blob(parentTime uint64) bool { + return cfg.IsL2BlobTimeSet() && *cfg.L2BlobConfig.L2BlobTime <= parentTime +} + +// UseInboxContract returns whether inbox contract is enabled +func (cfg *Config) UseInboxContract() bool { + return cfg.InboxContractConfig != nil && cfg.InboxContractConfig.UseInboxContract +} + +// IsL2BlobTimeSet returns whether l2 blob activation time is set +func (cfg *Config) IsL2BlobTimeSet() bool { + return cfg.L2BlobConfig != nil && cfg.L2BlobConfig.L2BlobTime != nil } // ValidateL1Config checks L1 config variables for errors. @@ -463,6 +491,17 @@ func (c *Config) IsInteropActivationBlock(l2BlockTime uint64) bool { !c.IsInterop(l2BlockTime-c.BlockTime) } +// IsActivationBlock returns the fork which activates at the block with time newTime if the previous +// block's time is oldTime. It return an empty ForkName if no fork activation takes place between +// those timestamps. It can be used for both, L1 and L2 blocks. +// TODO(12490): Currently only supports Holocene. Will be modularized in a follow-up. +func (c *Config) IsActivationBlock(oldTime, newTime uint64) ForkName { + if c.IsHolocene(newTime) && !c.IsHolocene(oldTime) { + return Holocene + } + return "" +} + func (c *Config) ActivateAtGenesis(hardfork ForkName) { // IMPORTANT! ordered from newest to oldest switch hardfork { @@ -649,6 +688,15 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { ) } +func (c *Config) ParseRollupConfig(in io.Reader) error { + dec := json.NewDecoder(in) + dec.DisallowUnknownFields() + if err := dec.Decode(c); err != nil { + return fmt.Errorf("failed to decode rollup config: %w", err) + } + return nil +} + func fmtForkTimeOrUnset(v *uint64) string { if v == nil { return "(not configured)" diff --git a/op-node/rollup/types_test.go b/op-node/rollup/types_test.go index 61c620be7f13c..11c4db505c96b 100644 --- a/op-node/rollup/types_test.go +++ b/op-node/rollup/types_test.go @@ -241,6 +241,15 @@ func TestActivations(t *testing.T) { return c.IsGranite(t) }, }, + { + name: "Holocene", + setUpgradeTime: func(t *uint64, c *Config) { + c.HoloceneTime = t + }, + checkEnabled: func(t uint64, c *Config) bool { + return c.IsHolocene(t) + }, + }, { name: "Interop", setUpgradeTime: func(t *uint64, c *Config) { @@ -692,3 +701,19 @@ func TestGetPayloadVersion(t *testing.T) { }) } } + +func TestConfig_IsActivationBlock(t *testing.T) { + ts := uint64(42) + // TODO(12490): Currently only supports Holocene. Will be modularized in a follow-up. + for _, fork := range []ForkName{Holocene} { + cfg := &Config{ + HoloceneTime: &ts, + } + require.Equal(t, fork, cfg.IsActivationBlock(0, ts)) + require.Equal(t, fork, cfg.IsActivationBlock(0, ts+64)) + require.Equal(t, fork, cfg.IsActivationBlock(ts-1, ts)) + require.Equal(t, fork, cfg.IsActivationBlock(ts-1, ts+1)) + require.Zero(t, cfg.IsActivationBlock(0, ts-1)) + require.Zero(t, cfg.IsActivationBlock(ts, ts+1)) + } +} diff --git a/op-node/service.go b/op-node/service.go index b24e2a638335d..fbb8ceac3fb7c 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -1,6 +1,7 @@ package opnode import ( + "context" "crypto/rand" "encoding/json" "errors" @@ -48,7 +49,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { driverConfig := NewDriverConfig(ctx) - p2pSignerSetup, err := p2pcli.LoadSignerSetup(ctx) + p2pSignerSetup, err := p2pcli.LoadSignerSetup(ctx, log) if err != nil { return nil, fmt.Errorf("failed to load p2p signer: %w", err) } @@ -80,7 +81,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ctx.IsSet(flags.HeartbeatURLFlag.Name) { log.Warn("Heartbeat functionality is not supported anymore, CLI flags will be removed in following release.") } - + conductorRPCEndpoint := ctx.String(flags.ConductorRpcFlag.Name) cfg := &node.Config{ L1: l1Endpoint, L2: l2Endpoint, @@ -108,11 +109,14 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { Sync: *syncConfig, RollupHalt: haltOption, - ConductorEnabled: ctx.Bool(flags.ConductorEnabledFlag.Name), - ConductorRpc: ctx.String(flags.ConductorRpcFlag.Name), + ConductorEnabled: ctx.Bool(flags.ConductorEnabledFlag.Name), + ConductorRpc: func(context.Context) (string, error) { + return conductorRPCEndpoint, nil + }, ConductorRpcTimeout: ctx.Duration(flags.ConductorRpcTimeoutFlag.Name), - AltDA: altda.ReadCLIConfig(ctx), + AltDA: altda.ReadCLIConfig(ctx), + DACConfig: node.ReadDACConfigFromCLI(ctx), } if err := cfg.LoadPersisted(log); err != nil { diff --git a/op-node/version/version.go b/op-node/version/version.go index 327ee7b497270..2456f656d45c1 100644 --- a/op-node/version/version.go +++ b/op-node/version/version.go @@ -1,6 +1,6 @@ package version var ( - Version = "v0.10.14" + Version = "v0.0.0" Meta = "dev" ) diff --git a/op-program/Dockerfile.repro b/op-program/Dockerfile.repro index e3293b7908f74..fd713162e0136 100644 --- a/op-program/Dockerfile.repro +++ b/op-program/Dockerfile.repro @@ -26,17 +26,16 @@ ARG OP_PROGRAM_VERSION=v0.0.0 ARG TARGETOS TARGETARCH -# Build the cannon, op-program, and op-program-client.elf binaries. +# Build the cannon and op-program-client.elf binaries. RUN --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \ GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION" -RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-program-host \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROGRAM_VERSION" RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-program-client-mips \ GOOS=linux GOARCH=mips GOMIPS=softfloat GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROGRAM_VERSION" # Run the op-program-client.elf binary directly through cannon's load-elf subcommand. -RUN /app/cannon/bin/cannon load-elf --type singlethreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.bin.gz --meta "" -RUN /app/cannon/bin/cannon load-elf --type multithreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "" +RUN /app/cannon/bin/cannon load-elf --type singlethreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.bin.gz --meta "/app/op-program/bin/meta.json" +RUN /app/cannon/bin/cannon load-elf --type multithreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "/app/op-program/bin/meta-mt.json" +RUN /app/cannon/bin/cannon load-elf --type multithreaded64 --path /app/op-program/bin/op-program-client64.elf --out /app/op-program/bin/prestate-mt64.bin.gz --meta "/app/op-program/bin/meta-mt64.json" # Generate the prestate proof containing the absolute pre-state hash. RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d.json' --output "" @@ -45,13 +44,21 @@ RUN mv /app/op-program/bin/0.json /app/op-program/bin/prestate-proof.json RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate-mt.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d-mt.json' --output "" RUN mv /app/op-program/bin/0-mt.json /app/op-program/bin/prestate-proof-mt.json +RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate-mt64.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d-mt64.json' --output "" +RUN mv /app/op-program/bin/0-mt64.json /app/op-program/bin/prestate-proof-mt64.json + # Exports files to the specified output location. # Writing files to host requires buildkit to be enabled. # e.g. `BUILDKIT=1 docker build ...` FROM scratch AS export-stage -COPY --from=builder /app/op-program/bin/op-program . COPY --from=builder /app/op-program/bin/op-program-client.elf . +COPY --from=builder /app/op-program/bin/op-program-client64.elf . +COPY --from=builder /app/op-program/bin/meta.json . COPY --from=builder /app/op-program/bin/prestate.bin.gz . COPY --from=builder /app/op-program/bin/prestate-proof.json . +COPY --from=builder /app/op-program/bin/meta-mt.json . COPY --from=builder /app/op-program/bin/prestate-mt.bin.gz . COPY --from=builder /app/op-program/bin/prestate-proof-mt.json . +COPY --from=builder /app/op-program/bin/meta-mt64.json . +COPY --from=builder /app/op-program/bin/prestate-mt64.bin.gz . +COPY --from=builder /app/op-program/bin/prestate-proof-mt64.json . diff --git a/op-program/Makefile b/op-program/Makefile index 80ad1e3bf580a..30cbaeea4c427 100644 --- a/op-program/Makefile +++ b/op-program/Makefile @@ -26,11 +26,18 @@ op-program-host: op-program-client: env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v -ldflags "$(PC_LDFLAGSSTRING)" -o ./bin/op-program-client ./client/cmd/main.go -op-program-client-mips: +op-program-client-mips: op-program-client-mips32 op-program-client-mips64 + +op-program-client-mips32: env GO111MODULE=on GOOS=linux GOARCH=mips GOMIPS=softfloat go build -v -ldflags "$(PC_LDFLAGSSTRING)" -o ./bin/op-program-client.elf ./client/cmd/main.go # verify output with: readelf -h bin/op-program-client.elf # result is mips32, big endian, R3000 +op-program-client-mips64: + env GO111MODULE=on GOOS=linux GOARCH=mips64 GOMIPS64=softfloat go build -v -ldflags "$(PC_LDFLAGSSTRING)" -o ./bin/op-program-client64.elf ./client/cmd/main.go + # verify output with: readelf -h bin/op-program-client64.elf + # result is mips64, big endian, R3000 + op-program-client-riscv: env GO111MODULE=on GOOS=linux GOARCH=riscv64 go build -v -gcflags="all=-d=softfloat" -ldflags "$(PC_LDFLAGSSTRING)" -o ./bin/op-program-client-riscv.elf ./client/cmd/main.go @@ -40,8 +47,16 @@ reproducible-prestate: @cat ./bin/prestate-proof.json | jq -r .pre @echo "MT-Cannon Absolute prestate hash: " @cat ./bin/prestate-proof-mt.json | jq -r .pre + @echo "Cannon64 Absolute prestate hash: " + @cat ./bin/prestate-proof-mt64.json | jq -r .pre .PHONY: reproducible-prestate +verify-reproducibility: + rm -rf temp/states + ./scripts/build-prestates.sh + env GO111MODULE=on go run ./prestates/verify/verify.go --input temp/states/versions.json +.PHONY: verify-reproducibility + clean: rm -rf bin "$(COMPAT_DIR)" @@ -51,7 +66,7 @@ test: verify-sepolia: op-program-host op-program-client env GO111MODULE=on go run ./verify/sepolia/cmd/sepolia.go --l1 $$SEPOLIA_L1URL --l1.beacon $$SEPOLIA_BEACON_URL --l2 $$SEPOLIA_L2URL --datadir /tmp/test-sepolia -verify-devnet: +verify-devnet: op-program-host op-program-client env GO111MODULE=on go run ./verify/devnet/cmd/devnet.go --l1 http://localhost:8545 --l1.beacon http://localhost:5052 --l2 http://localhost:9545 --datadir /tmp/test-devnet capture-mainnet-genesis: op-program-host op-program-client @@ -95,6 +110,8 @@ verify-compat: verify-sepolia-delta verify-sepolia-ecotone verify-mainnet-genesi op-program-host \ op-program-client \ op-program-client-mips \ + op-program-client-mips32 \ + op-program-client-mips64 \ op-program-client-riscv \ clean \ test \ diff --git a/op-program/README.md b/op-program/README.md index 15e89ffb5cf75..932ec85db22fb 100644 --- a/op-program/README.md +++ b/op-program/README.md @@ -45,6 +45,7 @@ After running `make reproducible-prestate`, the following files can be found in [./bin/](./bin/): - [`op-program`](./bin/op-program) - [`op-program-client.elf`](./bin/op-program-client.elf) +- [`op-program-client64.elf`](./bin/op-program-client64.elf) - [`prestate.bin.gz`](./bin/prestate.bin.gz) - [`prestate-proof.json`](./bin/prestate-proof.json) diff --git a/op-program/chainconfig/chaincfg.go b/op-program/chainconfig/chaincfg.go index 513d6d2a2ef8c..839207299a5c0 100644 --- a/op-program/chainconfig/chaincfg.go +++ b/op-program/chainconfig/chaincfg.go @@ -37,13 +37,10 @@ func rollupConfigByChainID(chainID uint64, customChainFS embed.FS) (*rollup.Conf } else if err != nil { return nil, fmt.Errorf("failed to get rollup config for chain ID %d: %w", chainID, err) } - dec := json.NewDecoder(file) - dec.DisallowUnknownFields() + defer file.Close() + var customRollupConfig rollup.Config - if err := dec.Decode(&customRollupConfig); err != nil { - return nil, fmt.Errorf("failed to parse rollup config for chain ID %d: %w", chainID, err) - } - return &customRollupConfig, nil + return &customRollupConfig, customRollupConfig.ParseRollupConfig(file) } func ChainConfigByChainID(chainID uint64) (*params.ChainConfig, error) { diff --git a/op-program/client/driver/driver.go b/op-program/client/driver/driver.go index fc232c5fcd43c..56fffb5b155ce 100644 --- a/op-program/client/driver/driver.go +++ b/op-program/client/driver/driver.go @@ -73,15 +73,14 @@ func (d *Driver) Emit(ev event.Event) { d.events = append(d.events, ev) } -var ExhaustErr = errors.New("exhausted events before completing program") - func (d *Driver) RunComplete() error { // Initial reset d.Emit(engine.ResetEngineRequestEvent{}) for !d.end.Closing() { if len(d.events) == 0 { - return ExhaustErr + d.logger.Info("Derivation complete: no further data to process") + return d.end.Result() } if len(d.events) > 10000 { // sanity check, in case of bugs. Better than going OOM. return errors.New("way too many events queued up, something is wrong") diff --git a/op-program/client/driver/driver_test.go b/op-program/client/driver/driver_test.go index 2f1249aff6a4c..8a5d02fe83f9b 100644 --- a/op-program/client/driver/driver_test.go +++ b/op-program/client/driver/driver_test.go @@ -92,7 +92,8 @@ func TestDriver(t *testing.T) { } count += 1 }) - require.ErrorIs(t, ExhaustErr, d.RunComplete()) + // No further processing to be done so evaluate if the claims output root is correct. + require.NoError(t, d.RunComplete()) }) t.Run("queued events", func(t *testing.T) { @@ -104,7 +105,7 @@ func TestDriver(t *testing.T) { } count += 1 }) - require.ErrorIs(t, ExhaustErr, d.RunComplete()) + require.NoError(t, d.RunComplete()) // add 1 for initial event that RunComplete fires require.Equal(t, 1+3*2, count, "must have queued up 2 events 3 times") }) diff --git a/op-program/client/driver/program.go b/op-program/client/driver/program.go index 0ef36f8f6a479..b83a1566c8ce0 100644 --- a/op-program/client/driver/program.go +++ b/op-program/client/driver/program.go @@ -63,9 +63,9 @@ func (d *ProgramDeriver) OnEvent(ev event.Event) bool { d.closing = true } case derive.DeriverIdleEvent: - // Not enough data to reach target - d.closing = true - d.logger.Info("Derivation complete: no further data to process") + // We dont't close the deriver yet, as the engine may still be processing events to reach + // the target. A ForkchoiceUpdateEvent will close the deriver when the target is reached. + d.logger.Info("Derivation complete: no further L1 data to process") case rollup.ResetEvent: d.closing = true d.result = fmt.Errorf("unexpected reset error: %w", x.Err) diff --git a/op-program/client/driver/program_test.go b/op-program/client/driver/program_test.go index 4c9941d754b4d..59206050a7f89 100644 --- a/op-program/client/driver/program_test.go +++ b/op-program/client/driver/program_test.go @@ -104,12 +104,12 @@ func TestProgramDeriver(t *testing.T) { require.NoError(t, p.result) }) }) - // on exhaustion of input data: stop without error + // Do not stop processing when the deriver is idle, the engine may still be busy and create further events. t.Run("deriver idle", func(t *testing.T) { p, m := newProgram(t, 1000) p.OnEvent(derive.DeriverIdleEvent{}) m.AssertExpectations(t) - require.True(t, p.closing) + require.False(t, p.closing) require.Nil(t, p.result) }) // on inconsistent chain data: stop with error diff --git a/op-program/client/l1/blob_fetcher.go b/op-program/client/l1/blob_fetcher.go index b41082e4d4d80..dd704bf8dbd0d 100644 --- a/op-program/client/l1/blob_fetcher.go +++ b/op-program/client/l1/blob_fetcher.go @@ -16,7 +16,7 @@ type BlobFetcher struct { oracle Oracle } -var _ = (*derive.L1BlobsFetcher)(nil) +var _ derive.L1BlobsFetcher = (*BlobFetcher)(nil) func NewBlobFetcher(logger log.Logger, oracle Oracle) *BlobFetcher { return &BlobFetcher{ diff --git a/op-program/client/l2/engineapi/block_processor.go b/op-program/client/l2/engineapi/block_processor.go index a2fee8eca7c1f..314019865ad85 100644 --- a/op-program/client/l2/engineapi/block_processor.go +++ b/op-program/client/l2/engineapi/block_processor.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -51,6 +52,14 @@ func NewBlockProcessorFromPayloadAttributes(provider BlockDataProvider, parent c Nonce: types.EncodeNonce(0), ParentBeaconRoot: attrs.ParentBeaconBlockRoot, } + if attrs.EIP1559Params != nil { + d, e := eip1559.DecodeHolocene1559Params(attrs.EIP1559Params[:]) + if d == 0 { + d = provider.Config().BaseFeeChangeDenominator(header.Time) + e = provider.Config().ElasticityMultiplier() + } + header.Extra = eip1559.EncodeHoloceneExtraData(d, e) + } return NewBlockProcessorFromHeader(provider, header) } @@ -90,7 +99,14 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (* // Blob tx not supported on optimism chains but fields must be set when Cancun is active. zero := uint64(0) header.BlobGasUsed = &zero - header.ExcessBlobGas = &zero + var excessBlobGas uint64 + if provider.Config().IsCancun(parentHeader.Number, parentHeader.Time) { + excessBlobGas = eip4844.CalcExcessBlobGas(*parentHeader.ExcessBlobGas, *parentHeader.BlobGasUsed) + } else { + // For the first post-fork block, both parent.data_gas_used and parent.excess_data_gas are evaluated as 0 + excessBlobGas = eip4844.CalcExcessBlobGas(0, 0) + } + header.ExcessBlobGas = &excessBlobGas } vmenv := mkEVM() core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, statedb) @@ -127,6 +143,9 @@ func (b *BlockProcessor) AddTx(tx *types.Transaction) error { } b.receipts = append(b.receipts, receipt) b.transactions = append(b.transactions, tx) + if b.header.BlobGasUsed != nil { + *b.header.BlobGasUsed += receipt.BlobGasUsed + } return nil } diff --git a/op-program/client/l2/engineapi/l2_engine_api.go b/op-program/client/l2/engineapi/l2_engine_api.go index 2f47cebbe5d62..273483893b375 100644 --- a/op-program/client/l2/engineapi/l2_engine_api.go +++ b/op-program/client/l2/engineapi/l2_engine_api.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/types" @@ -100,6 +101,9 @@ func computePayloadId(headBlockHash common.Hash, attrs *eth.PayloadAttributes) e hasher.Write(tx) } _ = binary.Write(hasher, binary.BigEndian, *attrs.GasLimit) + if attrs.EIP1559Params != nil { + hasher.Write(attrs.EIP1559Params[:]) + } var out engine.PayloadID copy(out[:], hasher.Sum(nil)[:8]) return out @@ -126,9 +130,9 @@ func (ea *L2EngineAPI) IncludeTx(tx *types.Transaction, from common.Address) err if ea.blockProcessor == nil { return ErrNotBuildingBlock } + if ea.l2ForceEmpty { ea.log.Info("Skipping including a transaction because e.L2ForceEmpty is true") - // t.InvalidAction("cannot include any sequencer txs") return nil } @@ -155,6 +159,7 @@ func (ea *L2EngineAPI) startBlock(parent common.Hash, attrs *eth.PayloadAttribut if err != nil { return err } + ea.blockProcessor = processor ea.pendingIndices = make(map[common.Address]uint64) ea.l2ForceEmpty = attrs.NoTxPool @@ -256,15 +261,27 @@ func (ea *L2EngineAPI) ForkchoiceUpdatedV3(ctx context.Context, state *eth.Forkc // Ported from: https://github.com/ethereum-optimism/op-geth/blob/c50337a60a1309a0f1dca3bf33ed1bb38c46cdd7/eth/catalyst/api.go#L206-L218 func (ea *L2EngineAPI) verifyPayloadAttributes(attr *eth.PayloadAttributes) error { c := ea.config() + t := uint64(attr.Timestamp) // Verify withdrawals attribute for Shanghai. - if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, c.LondonBlock, uint64(attr.Timestamp)); err != nil { + if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, c.LondonBlock, t); err != nil { return fmt.Errorf("invalid withdrawals: %w", err) } // Verify beacon root attribute for Cancun. - if err := checkAttribute(c.IsCancun, attr.ParentBeaconBlockRoot != nil, c.LondonBlock, uint64(attr.Timestamp)); err != nil { + if err := checkAttribute(c.IsCancun, attr.ParentBeaconBlockRoot != nil, c.LondonBlock, t); err != nil { return fmt.Errorf("invalid parent beacon block root: %w", err) } + // Verify EIP-1559 params for Holocene. + if c.IsHolocene(t) { + if attr.EIP1559Params == nil { + return errors.New("got nil eip-1559 params while Holocene is active") + } + if err := eip1559.ValidateHolocene1559Params(attr.EIP1559Params[:]); err != nil { + return fmt.Errorf("invalid Holocene params: %w", err) + } + } else if attr.EIP1559Params != nil { + return errors.New("got Holocene params though fork not active") + } return nil } @@ -318,6 +335,13 @@ func (ea *L2EngineAPI) NewPayloadV3(ctx context.Context, params *eth.ExecutionPa return ð.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun")) } + // Payload must have eip-1559 params in ExtraData after Holocene + if ea.config().IsHolocene(uint64(params.Timestamp)) { + if err := eip1559.ValidateHoloceneExtraData(params.ExtraData); err != nil { + return ð.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.UnsupportedFork.With(errors.New("invalid holocene extraData post-holoocene")) + } + } + return ea.newPayload(ctx, params, versionedHashes, beaconRoot) } diff --git a/op-program/client/l2/engineapi/l2_engine_api_test.go b/op-program/client/l2/engineapi/l2_engine_api_test.go index f6db20ecc44cc..adab5dc3c0885 100644 --- a/op-program/client/l2/engineapi/l2_engine_api_test.go +++ b/op-program/client/l2/engineapi/l2_engine_api_test.go @@ -28,6 +28,7 @@ func TestCreatedBlocksAreCached(t *testing.T) { require.NotNil(t, engineAPI) genesis := backend.GetHeaderByNumber(0) genesisHash := genesis.Hash() + eip1559Params := eth.Bytes8([]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}) result, err := engineAPI.ForkchoiceUpdatedV3(context.Background(), ð.ForkchoiceState{ HeadBlockHash: genesisHash, SafeBlockHash: genesisHash, @@ -40,6 +41,7 @@ func TestCreatedBlocksAreCached(t *testing.T) { ParentBeaconBlockRoot: &common.Hash{0x22}, NoTxPool: false, GasLimit: (*eth.Uint64Quantity)(&genesis.GasLimit), + EIP1559Params: &eip1559Params, }) require.NoError(t, err) require.EqualValues(t, engine.VALID, result.PayloadStatus.Status) @@ -81,13 +83,25 @@ func newStubBackend(t *testing.T) *stubCachingBackend { } func createGenesis() *core.Genesis { + config := *params.MergedTestChainConfig + var zero uint64 + // activate recent OP-stack forks + config.RegolithTime = &zero + config.CanyonTime = &zero + config.EcotoneTime = &zero + config.FjordTime = &zero + config.GraniteTime = &zero + config.HoloceneTime = &zero + l2Genesis := &core.Genesis{ - Config: params.MergedTestChainConfig, // Arbitrary post-merge config + Config: &config, Difficulty: common.Big0, ParentHash: common.Hash{}, BaseFee: big.NewInt(7), Alloc: map[common.Address]types.Account{}, + ExtraData: []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, // for Holocene eip-1559 params } + return l2Genesis } diff --git a/op-program/host/cmd/main_test.go b/op-program/host/cmd/main_test.go index cee90a9566463..9994e87100bd5 100644 --- a/op-program/host/cmd/main_test.go +++ b/op-program/host/cmd/main_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-program/chainconfig" + "github.com/ethereum-optimism/optimism/op-program/client" "github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/types" oplog "github.com/ethereum-optimism/optimism/op-service/log" @@ -160,6 +161,30 @@ func TestL2Genesis(t *testing.T) { }) } +func TestL2ChainID(t *testing.T) { + t.Run("DefaultToNetworkChainID", func(t *testing.T) { + cfg := configForArgs(t, replaceRequiredArg("--network", "op-mainnet")) + require.Equal(t, uint64(10), cfg.L2ChainID) + }) + + t.Run("DefaultToGenesisChainID", func(t *testing.T) { + rollupCfgFile := writeValidRollupConfig(t) + genesisFile := writeValidGenesis(t) + cfg := configForArgs(t, addRequiredArgsExcept("--network", "--rollup.config", rollupCfgFile, "--l2.genesis", genesisFile)) + require.Equal(t, l2GenesisConfig.ChainID.Uint64(), cfg.L2ChainID) + }) + + t.Run("OverrideToCustomIndicator", func(t *testing.T) { + rollupCfgFile := writeValidRollupConfig(t) + genesisFile := writeValidGenesis(t) + cfg := configForArgs(t, addRequiredArgsExcept("--network", + "--rollup.config", rollupCfgFile, + "--l2.genesis", genesisFile, + "--l2.custom")) + require.Equal(t, client.CustomChainIDIndicator, cfg.L2ChainID) + }) +} + func TestL2Head(t *testing.T) { t.Run("Required", func(t *testing.T) { verifyArgsInvalid(t, "flag l2.head is required", addRequiredArgsExcept("--l2.head")) @@ -274,6 +299,19 @@ func TestL2Claim(t *testing.T) { }) } +func TestL2Experimental(t *testing.T) { + t.Run("DefaultEmpty", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs()) + require.Equal(t, cfg.L2ExperimentalURL, "") + }) + + t.Run("Valid", func(t *testing.T) { + expected := "https://example.com:8545" + cfg := configForArgs(t, replaceRequiredArg("--l2.experimental", expected)) + require.EqualValues(t, expected, cfg.L2ExperimentalURL) + }) +} + func TestL2BlockNumber(t *testing.T) { t.Run("Required", func(t *testing.T) { verifyArgsInvalid(t, "flag l2.blocknumber is required", addRequiredArgsExcept("--l2.blocknumber")) diff --git a/op-program/host/config/config.go b/op-program/host/config/config.go index c04f959a8b7de..7daf2d17918ab 100644 --- a/op-program/host/config/config.go +++ b/op-program/host/config/config.go @@ -6,11 +6,13 @@ import ( "fmt" "os" "slices" + "strconv" "github.com/ethereum-optimism/optimism/op-node/chaincfg" + "github.com/ethereum-optimism/optimism/op-program/chainconfig" + "github.com/ethereum-optimism/optimism/op-program/client" "github.com/ethereum-optimism/optimism/op-program/host/types" - opnode "github.com/ethereum-optimism/optimism/op-node" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-program/host/flags" "github.com/ethereum-optimism/optimism/op-service/sources" @@ -36,7 +38,8 @@ var ( ) type Config struct { - Rollup *rollup.Config + L2ChainID uint64 + Rollup *rollup.Config // DataDir is the directory to read/write pre-image data from/to. // If not set, an in-memory key-value store is used and fetching data must be enabled DataDir string @@ -55,7 +58,11 @@ type Config struct { L2Head common.Hash // L2OutputRoot is the agreed L2 output root to start derivation from L2OutputRoot common.Hash - L2URL string + // L2URL is the URL of the L2 node to fetch L2 data from, this is the canonical URL for L2 data + // This URL is used as a fallback for L2ExperimentalURL if the experimental URL fails or cannot retrieve the desired data + L2URL string + // L2ExperimentalURL is the URL of the L2 node (non hash db archival node, for example, reth archival node) to fetch L2 data from + L2ExperimentalURL string // L2Claim is the claimed L2 output root to verify L2Claim common.Hash // L2ClaimBlockNumber is the block number the claimed L2 output root is from @@ -70,9 +77,6 @@ type Config struct { // ServerMode indicates that the program should run in pre-image server mode and wait for requests. // No client program is run. ServerMode bool - - // IsCustomChainConfig indicates that the program uses a custom chain configuration - IsCustomChainConfig bool } func (c *Config) Check() error { @@ -126,19 +130,23 @@ func NewConfig( l2Claim common.Hash, l2ClaimBlockNum uint64, ) *Config { - _, err := params.LoadOPStackChainConfig(l2Genesis.ChainID.Uint64()) - isCustomConfig := err != nil + l2ChainID := l2Genesis.ChainID.Uint64() + _, err := params.LoadOPStackChainConfig(l2ChainID) + if err != nil { + // Unknown chain ID so assume it is custom + l2ChainID = client.CustomChainIDIndicator + } return &Config{ - Rollup: rollupCfg, - L2ChainConfig: l2Genesis, - L1Head: l1Head, - L2Head: l2Head, - L2OutputRoot: l2OutputRoot, - L2Claim: l2Claim, - L2ClaimBlockNumber: l2ClaimBlockNum, - L1RPCKind: sources.RPCKindStandard, - IsCustomChainConfig: isCustomConfig, - DataFormat: types.DataFormatDirectory, + L2ChainID: l2ChainID, + Rollup: rollupCfg, + L2ChainConfig: l2Genesis, + L1Head: l1Head, + L2Head: l2Head, + L2OutputRoot: l2OutputRoot, + L2Claim: l2Claim, + L2ClaimBlockNumber: l2ClaimBlockNum, + L1RPCKind: sources.RPCKindStandard, + DataFormat: types.DataFormatDirectory, } } @@ -146,10 +154,7 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { if err := flags.CheckRequired(ctx); err != nil { return nil, err } - rollupCfg, err := opnode.NewRollupConfigFromCLI(log, ctx) - if err != nil { - return nil, err - } + l2Head := common.HexToHash(ctx.String(flags.L2Head.Name)) if l2Head == (common.Hash{}) { return nil, ErrInvalidL2Head @@ -171,49 +176,74 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { if l1Head == (common.Hash{}) { return nil, ErrInvalidL1Head } - l2GenesisPath := ctx.String(flags.L2GenesisPath.Name) + + var err error + var rollupCfg *rollup.Config var l2ChainConfig *params.ChainConfig - var isCustomConfig bool - if l2GenesisPath == "" { - networkName := ctx.String(flags.Network.Name) - ch := chaincfg.ChainByName(networkName) - if ch == nil { - return nil, fmt.Errorf("flag %s is required for network %s", flags.L2GenesisPath.Name, networkName) + var l2ChainID uint64 + networkName := ctx.String(flags.Network.Name) + if networkName != "" { + var chainID uint64 + if chainID, err = strconv.ParseUint(networkName, 10, 64); err != nil { + ch := chaincfg.ChainByName(networkName) + if ch == nil { + return nil, fmt.Errorf("invalid network: %q", networkName) + } + chainID = ch.ChainID + } + + l2ChainConfig, err = chainconfig.ChainConfigByChainID(chainID) + if err != nil { + return nil, fmt.Errorf("failed to load chain config for chain %d: %w", chainID, err) } - cfg, err := params.LoadOPStackChainConfig(ch.ChainID) + rollupCfg, err = chainconfig.RollupConfigByChainID(chainID) if err != nil { - return nil, fmt.Errorf("failed to load chain config for chain %d: %w", ch.ChainID, err) + return nil, fmt.Errorf("failed to load rollup config for chain %d: %w", chainID, err) } - l2ChainConfig = cfg + l2ChainID = chainID } else { + l2GenesisPath := ctx.String(flags.L2GenesisPath.Name) l2ChainConfig, err = loadChainConfigFromGenesis(l2GenesisPath) - isCustomConfig = true - } - if err != nil { - return nil, fmt.Errorf("invalid genesis: %w", err) + if err != nil { + return nil, fmt.Errorf("invalid genesis: %w", err) + } + + rollupConfigPath := ctx.String(flags.RollupConfig.Name) + rollupCfg, err = loadRollupConfig(rollupConfigPath) + if err != nil { + return nil, fmt.Errorf("invalid rollup config: %w", err) + } + + l2ChainID = l2ChainConfig.ChainID.Uint64() + if ctx.Bool(flags.L2Custom.Name) { + log.Warn("Using custom chain configuration via preimage oracle. This is not compatible with on-chain execution.") + l2ChainID = client.CustomChainIDIndicator + } } + dbFormat := types.DataFormat(ctx.String(flags.DataFormat.Name)) if !slices.Contains(types.SupportedDataFormats, dbFormat) { return nil, fmt.Errorf("invalid %w: %v", ErrInvalidDataFormat, dbFormat) } return &Config{ - Rollup: rollupCfg, - DataDir: ctx.String(flags.DataDir.Name), - DataFormat: dbFormat, - L2URL: ctx.String(flags.L2NodeAddr.Name), - L2ChainConfig: l2ChainConfig, - L2Head: l2Head, - L2OutputRoot: l2OutputRoot, - L2Claim: l2Claim, - L2ClaimBlockNumber: l2ClaimBlockNum, - L1Head: l1Head, - L1URL: ctx.String(flags.L1NodeAddr.Name), - L1BeaconURL: ctx.String(flags.L1BeaconAddr.Name), - L1TrustRPC: ctx.Bool(flags.L1TrustRPC.Name), - L1RPCKind: sources.RPCProviderKind(ctx.String(flags.L1RPCProviderKind.Name)), - ExecCmd: ctx.String(flags.Exec.Name), - ServerMode: ctx.Bool(flags.Server.Name), - IsCustomChainConfig: isCustomConfig, + L2ChainID: l2ChainID, + Rollup: rollupCfg, + DataDir: ctx.String(flags.DataDir.Name), + DataFormat: dbFormat, + L2URL: ctx.String(flags.L2NodeAddr.Name), + L2ExperimentalURL: ctx.String(flags.L2NodeExperimentalAddr.Name), + L2ChainConfig: l2ChainConfig, + L2Head: l2Head, + L2OutputRoot: l2OutputRoot, + L2Claim: l2Claim, + L2ClaimBlockNumber: l2ClaimBlockNum, + L1Head: l1Head, + L1URL: ctx.String(flags.L1NodeAddr.Name), + L1BeaconURL: ctx.String(flags.L1BeaconAddr.Name), + L1TrustRPC: ctx.Bool(flags.L1TrustRPC.Name), + L1RPCKind: sources.RPCProviderKind(ctx.String(flags.L1RPCProviderKind.Name)), + ExecCmd: ctx.String(flags.Exec.Name), + ServerMode: ctx.Bool(flags.Server.Name), }, nil } @@ -229,3 +259,14 @@ func loadChainConfigFromGenesis(path string) (*params.ChainConfig, error) { } return genesis.Config, nil } + +func loadRollupConfig(rollupConfigPath string) (*rollup.Config, error) { + file, err := os.Open(rollupConfigPath) + if err != nil { + return nil, fmt.Errorf("failed to read rollup config: %w", err) + } + defer file.Close() + + var rollupConfig rollup.Config + return &rollupConfig, rollupConfig.ParseRollupConfig(file) +} diff --git a/op-program/host/config/config_test.go b/op-program/host/config/config_test.go index 971807d8d6b96..5c812f88fdae1 100644 --- a/op-program/host/config/config_test.go +++ b/op-program/host/config/config_test.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-program/chainconfig" + "github.com/ethereum-optimism/optimism/op-program/client" "github.com/ethereum-optimism/optimism/op-program/host/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" @@ -163,15 +164,15 @@ func TestRejectExecAndServerMode(t *testing.T) { require.ErrorIs(t, err, ErrNoExecInServerMode) } -func TestIsCustomChainConfig(t *testing.T) { +func TestCustomL2ChainID(t *testing.T) { t.Run("nonCustom", func(t *testing.T) { cfg := validConfig() - require.Equal(t, cfg.IsCustomChainConfig, false) + require.Equal(t, cfg.L2ChainID, validL2Genesis.ChainID.Uint64()) }) t.Run("custom", func(t *testing.T) { customChainConfig := ¶ms.ChainConfig{ChainID: big.NewInt(0x1212121212)} cfg := NewConfig(validRollupConfig, customChainConfig, validL1Head, validL2Head, validL2OutputRoot, validL2Claim, validL2ClaimBlockNum) - require.Equal(t, cfg.IsCustomChainConfig, true) + require.Equal(t, cfg.L2ChainID, client.CustomChainIDIndicator) }) } diff --git a/op-program/host/flags/flags.go b/op-program/host/flags/flags.go index 5eb633c7202f5..a8d5798c06c68 100644 --- a/op-program/host/flags/flags.go +++ b/op-program/host/flags/flags.go @@ -21,6 +21,14 @@ func prefixEnvVars(name string) []string { } var ( + L2Custom = &cli.BoolFlag{ + Name: "l2.custom", + Usage: "Override the L2 chain ID to the custom chain indicator for custom chain configuration not present in the client program. " + + "WARNING: This is not compatible with on-chain execution and must only be used for testing.", + EnvVars: prefixEnvVars("L2_CHAINID"), + Value: false, + Hidden: true, + } RollupConfig = &cli.StringFlag{ Name: "rollup.config", Usage: "Rollup chain parameters", @@ -47,6 +55,11 @@ var ( Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)", EnvVars: prefixEnvVars("L2_RPC"), } + L2NodeExperimentalAddr = &cli.StringFlag{ + Name: "l2.experimental", + Usage: "Address of L2 JSON-RPC endpoint to use for experimental features (debug_executionWitness)", + EnvVars: prefixEnvVars("L2_RPC_EXPERIMENTAL_RPC"), + } L1Head = &cli.StringFlag{ Name: "l1.head", Usage: "Hash of the L1 head block. Derivation stops after this block is processed.", @@ -126,11 +139,13 @@ var requiredFlags = []cli.Flag{ } var programFlags = []cli.Flag{ + L2Custom, RollupConfig, Network, DataDir, DataFormat, L2NodeAddr, + L2NodeExperimentalAddr, L2GenesisPath, L1NodeAddr, L1BeaconAddr, @@ -158,6 +173,12 @@ func CheckRequired(ctx *cli.Context) error { if network == "" && ctx.String(L2GenesisPath.Name) == "" { return fmt.Errorf("flag %s is required for custom networks", L2GenesisPath.Name) } + if ctx.String(L2GenesisPath.Name) != "" && network != "" { + return fmt.Errorf("cannot specify both %s and %s", L2GenesisPath.Name, Network.Name) + } + if ctx.Bool(L2Custom.Name) && rollupConfig == "" { + return fmt.Errorf("flag %s cannot be used with named networks", L2Custom.Name) + } for _, flag := range requiredFlags { if !ctx.IsSet(flag.Names()[0]) { return fmt.Errorf("flag %s is required", flag.Names()[0]) diff --git a/op-program/host/host.go b/op-program/host/host.go index a60e451b972d4..cd30421f9383a 100644 --- a/op-program/host/host.go +++ b/op-program/host/host.go @@ -24,11 +24,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -type L2Source struct { - *L2Client - *sources.DebugClient -} - type Prefetcher interface { Hint(hint string) error GetPreimage(ctx context.Context, key common.Hash) ([]byte, error) @@ -230,31 +225,28 @@ func makeDefaultPrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV return nil, nil } logger.Info("Connecting to L1 node", "l1", cfg.L1URL) - l1RPC, err := client.NewRPC(ctx, logger, cfg.L1URL, client.WithDialBackoff(10)) + l1RPC, err := client.NewRPC(ctx, logger, cfg.L1URL, client.WithDialAttempts(10)) if err != nil { return nil, fmt.Errorf("failed to setup L1 RPC: %w", err) } - logger.Info("Connecting to L2 node", "l2", cfg.L2URL) - l2RPC, err := client.NewRPC(ctx, logger, cfg.L2URL, client.WithDialBackoff(10)) - if err != nil { - return nil, fmt.Errorf("failed to setup L2 RPC: %w", err) - } - l1ClCfg := sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind) - l2ClCfg := sources.L2ClientDefaultConfig(cfg.Rollup, true) l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg) if err != nil { return nil, fmt.Errorf("failed to create L1 client: %w", err) } + + logger.Info("Connecting to L1 beacon", "l1", cfg.L1BeaconURL) l1Beacon := sources.NewBeaconHTTPClient(client.NewBasicHTTPClient(cfg.L1BeaconURL, logger)) l1BlobFetcher := sources.NewL1BeaconClient(l1Beacon, sources.L1BeaconClientConfig{FetchAllSidecars: false}) - l2Cl, err := NewL2Client(l2RPC, logger, nil, &L2ClientConfig{L2ClientConfig: l2ClCfg, L2Head: cfg.L2Head}) + + logger.Info("Initializing L2 clients") + l2Client, err := NewL2Source(ctx, logger, cfg) if err != nil { - return nil, fmt.Errorf("failed to create L2 client: %w", err) + return nil, fmt.Errorf("failed to create L2 source: %w", err) } - l2DebugCl := &L2Source{L2Client: l2Cl, DebugClient: sources.NewDebugClient(l2RPC.CallContext)} - return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv), nil + + return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2Client, kv), nil } func routeHints(logger log.Logger, hHostRW io.ReadWriter, hinter preimage.HintHandler) chan error { diff --git a/op-program/host/kvstore/local.go b/op-program/host/kvstore/local.go index b81b427e4150f..1c1bfa8032700 100644 --- a/op-program/host/kvstore/local.go +++ b/op-program/host/kvstore/local.go @@ -38,22 +38,14 @@ func (s *LocalPreimageSource) Get(key common.Hash) ([]byte, error) { case l2ClaimBlockNumberKey: return binary.BigEndian.AppendUint64(nil, s.config.L2ClaimBlockNumber), nil case l2ChainIDKey: - // The CustomChainIDIndicator informs the client to rely on the L2ChainConfigKey to - // read the chain config. Otherwise, it'll attempt to read a non-existent hardcoded chain config - var chainID uint64 - if s.config.IsCustomChainConfig { - chainID = client.CustomChainIDIndicator - } else { - chainID = s.config.L2ChainConfig.ChainID.Uint64() - } - return binary.BigEndian.AppendUint64(nil, chainID), nil + return binary.BigEndian.AppendUint64(nil, s.config.L2ChainID), nil case l2ChainConfigKey: - if !s.config.IsCustomChainConfig { + if s.config.L2ChainID != client.CustomChainIDIndicator { return nil, ErrNotFound } return json.Marshal(s.config.L2ChainConfig) case rollupKey: - if !s.config.IsCustomChainConfig { + if s.config.L2ChainID != client.CustomChainIDIndicator { return nil, ErrNotFound } return json.Marshal(s.config.Rollup) diff --git a/op-program/host/kvstore/local_test.go b/op-program/host/kvstore/local_test.go index 7d379024aa2eb..6402f731cac73 100644 --- a/op-program/host/kvstore/local_test.go +++ b/op-program/host/kvstore/local_test.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/chaincfg" preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum-optimism/optimism/op-program/client" "github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" @@ -15,6 +16,7 @@ import ( func TestLocalPreimageSource(t *testing.T) { cfg := &config.Config{ + L2ChainID: 86, Rollup: chaincfg.OPSepolia(), L1Head: common.HexToHash("0x1111"), L2OutputRoot: common.HexToHash("0x2222"), @@ -32,7 +34,7 @@ func TestLocalPreimageSource(t *testing.T) { {"L2OutputRoot", l2OutputRootKey, cfg.L2OutputRoot.Bytes()}, {"L2Claim", l2ClaimKey, cfg.L2Claim.Bytes()}, {"L2ClaimBlockNumber", l2ClaimBlockNumberKey, binary.BigEndian.AppendUint64(nil, cfg.L2ClaimBlockNumber)}, - {"L2ChainID", l2ChainIDKey, binary.BigEndian.AppendUint64(nil, cfg.L2ChainConfig.ChainID.Uint64())}, + {"L2ChainID", l2ChainIDKey, binary.BigEndian.AppendUint64(nil, 86)}, {"Rollup", rollupKey, nil}, // Only available for custom chain configs {"ChainConfig", l2ChainConfigKey, nil}, // Only available for custom chain configs {"Unknown", preimage.LocalIndexKey(1000).PreimageKey(), nil}, @@ -52,13 +54,13 @@ func TestLocalPreimageSource(t *testing.T) { func TestGetCustomChainConfigPreimages(t *testing.T) { cfg := &config.Config{ - Rollup: chaincfg.OPSepolia(), - IsCustomChainConfig: true, - L1Head: common.HexToHash("0x1111"), - L2OutputRoot: common.HexToHash("0x2222"), - L2Claim: common.HexToHash("0x3333"), - L2ClaimBlockNumber: 1234, - L2ChainConfig: params.SepoliaChainConfig, + Rollup: chaincfg.OPSepolia(), + L2ChainID: client.CustomChainIDIndicator, + L1Head: common.HexToHash("0x1111"), + L2OutputRoot: common.HexToHash("0x2222"), + L2Claim: common.HexToHash("0x3333"), + L2ClaimBlockNumber: 1234, + L2ChainConfig: params.SepoliaChainConfig, } source := NewLocalPreimageSource(cfg) actualRollup, err := source.Get(rollupKey) diff --git a/op-program/host/l2_source.go b/op-program/host/l2_source.go new file mode 100644 index 0000000000000..38906f70b87a0 --- /dev/null +++ b/op-program/host/l2_source.go @@ -0,0 +1,151 @@ +package host + +import ( + "context" + "time" + + "github.com/ethereum-optimism/optimism/op-program/host/config" + "github.com/ethereum-optimism/optimism/op-program/host/prefetcher" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// L2Source is a source of L2 data, it abstracts away the details of how to fetch L2 data between canonical and experimental sources. +// It also tracks metrics for each of the apis. Once experimental sources are stable, this will only route to the "experimental" source. +type L2Source struct { + logger log.Logger + + // canonical source, used as a fallback if experimental source is enabled but fails + // the underlying node should be a geth hash scheme archival node. + canonicalEthClient *L2Client + canonicalDebugClient *sources.DebugClient + + // experimental source, used as the primary source if enabled + experimentalClient *L2Client +} + +var _ prefetcher.L2Source = &L2Source{} + +// NewL2SourceWithClient creates a new L2 source with the given client as the canonical client. +// This doesn't configure the experimental source, but is useful for testing. +func NewL2SourceWithClient(logger log.Logger, canonicalL2Client *L2Client, canonicalDebugClient *sources.DebugClient) *L2Source { + source := &L2Source{ + logger: logger, + canonicalEthClient: canonicalL2Client, + canonicalDebugClient: canonicalDebugClient, + } + + return source +} + +func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config) (*L2Source, error) { + logger.Info("Connecting to canonical L2 source", "url", config.L2URL) + + // eth_getProof calls are expensive and takes time, so we use a longer timeout + canonicalL2RPC, err := client.NewRPC(ctx, logger, config.L2URL, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute)) + if err != nil { + return nil, err + } + canonicalDebugClient := sources.NewDebugClient(canonicalL2RPC.CallContext) + + canonicalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true) + canonicalL2Client, err := NewL2Client(canonicalL2RPC, logger, nil, &L2ClientConfig{L2ClientConfig: canonicalL2ClientCfg, L2Head: config.L2Head}) + if err != nil { + return nil, err + } + + source := NewL2SourceWithClient(logger, canonicalL2Client, canonicalDebugClient) + + if len(config.L2ExperimentalURL) == 0 { + return source, nil + } + + logger.Info("Connecting to experimental L2 source", "url", config.L2ExperimentalURL) + // debug_executionWitness calls are expensive and takes time, so we use a longer timeout + experimentalRPC, err := client.NewRPC(ctx, logger, config.L2ExperimentalURL, client.WithDialAttempts(10), client.WithCallTimeout(5*time.Minute)) + if err != nil { + return nil, err + } + experimentalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true) + experimentalL2Client, err := NewL2Client(experimentalRPC, logger, nil, &L2ClientConfig{L2ClientConfig: experimentalL2ClientCfg, L2Head: config.L2Head}) + if err != nil { + return nil, err + } + + source.experimentalClient = experimentalL2Client + + return source, nil +} + +func (l *L2Source) ExperimentalEnabled() bool { + return l.experimentalClient != nil +} + +// CodeByHash implements prefetcher.L2Source. +func (l *L2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) { + if l.ExperimentalEnabled() { + // This means experimental source was not able to retrieve relevant information from eth_getProof or debug_executionWitness + // We should fall back to the canonical source, and log a warning, and record a metric + l.logger.Warn("Experimental source failed to retrieve code by hash, falling back to canonical source", "hash", hash) + } + return l.canonicalDebugClient.CodeByHash(ctx, hash) +} + +// NodeByHash implements prefetcher.L2Source. +func (l *L2Source) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) { + if l.ExperimentalEnabled() { + // This means experimental source was not able to retrieve relevant information from eth_getProof or debug_executionWitness + // We should fall back to the canonical source, and log a warning, and record a metric + l.logger.Warn("Experimental source failed to retrieve node by hash, falling back to canonical source", "hash", hash) + } + return l.canonicalDebugClient.NodeByHash(ctx, hash) +} + +// InfoAndTxsByHash implements prefetcher.L2Source. +func (l *L2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) { + if l.ExperimentalEnabled() { + return l.experimentalClient.InfoAndTxsByHash(ctx, blockHash) + } + return l.canonicalEthClient.InfoAndTxsByHash(ctx, blockHash) +} + +// OutputByRoot implements prefetcher.L2Source. +func (l *L2Source) OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) { + if l.ExperimentalEnabled() { + return l.experimentalClient.OutputByRoot(ctx, root) + } + return l.canonicalEthClient.OutputByRoot(ctx, root) +} + +// ExecutionWitness implements prefetcher.L2Source. +func (l *L2Source) ExecutionWitness(ctx context.Context, blockNum uint64) (*eth.ExecutionWitness, error) { + if !l.ExperimentalEnabled() { + l.logger.Error("Experimental source is not enabled, cannot fetch execution witness", "blockNum", blockNum) + return nil, prefetcher.ErrExperimentalPrefetchDisabled + } + + // log errors, but return standard error so we know to retry with legacy source + witness, err := l.experimentalClient.ExecutionWitness(ctx, blockNum) + if err != nil { + l.logger.Error("Failed to fetch execution witness from experimental source", "blockNum", blockNum, "err", err) + return nil, prefetcher.ErrExperimentalPrefetchFailed + } + return witness, nil +} + +// GetProof implements prefetcher.L2Source. +func (l *L2Source) GetProof(ctx context.Context, address common.Address, storage []common.Hash, blockTag string) (*eth.AccountResult, error) { + if l.ExperimentalEnabled() { + return l.experimentalClient.GetProof(ctx, address, storage, blockTag) + } + proof, err := l.canonicalEthClient.GetProof(ctx, address, storage, blockTag) + if err != nil { + l.logger.Error("Failed to fetch proof from canonical source", "address", address, "storage", storage, "blockTag", blockTag, "err", err) + return nil, prefetcher.ErrExperimentalPrefetchFailed + } + return proof, nil +} diff --git a/op-program/host/prefetcher/prefetcher.go b/op-program/host/prefetcher/prefetcher.go index cb5a52fa88cec..60aa67d633608 100644 --- a/op-program/host/prefetcher/prefetcher.go +++ b/op-program/host/prefetcher/prefetcher.go @@ -28,6 +28,11 @@ var ( precompileFailure = [1]byte{0} ) +var ( + ErrExperimentalPrefetchFailed = errors.New("experimental prefetch failed") + ErrExperimentalPrefetchDisabled = errors.New("experimental prefetch disabled") +) + var acceleratedPrecompiles = []common.Address{ common.BytesToAddress([]byte{0x1}), // ecrecover common.BytesToAddress([]byte{0x8}), // bn256Pairing diff --git a/op-program/host/version/version.go b/op-program/host/version/version.go index 327ee7b497270..2456f656d45c1 100644 --- a/op-program/host/version/version.go +++ b/op-program/host/version/version.go @@ -1,6 +1,6 @@ package version var ( - Version = "v0.10.14" + Version = "v0.0.0" Meta = "dev" ) diff --git a/op-program/prestates/releases.go b/op-program/prestates/releases.go new file mode 100644 index 0000000000000..6fa6225d6c1ac --- /dev/null +++ b/op-program/prestates/releases.go @@ -0,0 +1,30 @@ +package prestates + +// This package is imported by the superchain-registry as part of chain validation +// tests. Please do not delete these files unless the downstream dependency is removed. + +import ( + _ "embed" + "encoding/json" + "fmt" +) + +//go:embed releases.json +var releasesJSON []byte + +type Release struct { + Version string `json:"version"` + Hash string `json:"hash"` + GovernanceApproved bool `json:"governanceApproved"` +} + +// GetReleases reads the contents of the releases.json file +func GetReleases() ([]Release, error) { + var releases []Release + err := json.Unmarshal(releasesJSON, &releases) + if err != nil { + return nil, fmt.Errorf("failed to parse JSON: %w", err) + } + + return releases, nil +} diff --git a/op-program/prestates/releases.json b/op-program/prestates/releases.json new file mode 100644 index 0000000000000..75106c4b3c998 --- /dev/null +++ b/op-program/prestates/releases.json @@ -0,0 +1,72 @@ +[ + { + "version": "1.4.0-rc.2", + "hash": "0x0364e4e72922e7d649338f558f8a14b50ca31922a1484e73ea03987fb1516095" + }, { + "version": "1.4.0-rc.1", + "hash": "0x03925193e3e89f87835bbdf3a813f60b2aa818a36bbe71cd5d8fd7e79f5e8afe" + }, + { + "version": "1.3.1", + "hash": "0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c", + "governanceApproved": true + }, + { + "version": "1.3.1-rc.2", + "hash": "0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c" + }, + { + "version": "1.3.1-rc.1", + "hash": "0x03e806a2859a875267a563462a06d4d1d1b455a9efee959a46e21e54b6caf69a" + }, + { + "version": "1.3.0-rc.3", + "hash": "0x030de10d9da911a2b180ecfae2aeaba8758961fc28262ce989458c6f9a547922" + }, + { + "version": "1.3.0-rc.2", + "hash": "0x0385c3f8ee78491001d92b90b07d0cf387b7b52ab9b83b4d87c994e92cf823ba" + }, + { + "version": "1.3.0-rc.1", + "hash": "0x0367c4aa897bffbded0b523f277ca892298dc3c691baf37bc2099b86024f9673" + }, + { + "version": "1.2.0", + "hash": "0x03617abec0b255dc7fc7a0513a2c2220140a1dcd7a1c8eca567659bd67e05cea", + "governanceApproved": true + }, + { + "version": "1.1.0", + "hash": "0x03e69d3de5155f4a80da99dd534561cbddd4f9dd56c9ecc704d6886625711d2b" + }, + { + "version": "1.0.1", + "hash": "0x0398bdd93e2e9313befdf82beb709da6a4daf35ce1abb42d8a998ec9bc1c572e" + }, + { + "version": "1.0.0", + "hash": "0x037ef3c1a487960b0e633d3e513df020c43432769f41a634d18a9595cbf53c55", + "governanceApproved": true + }, + { + "version": "0.3.1", + "hash": "0x037ef3c1a487960b0e633d3e513df020c43432769f41a634d18a9595cbf53c55" + }, + { + "version": "0.3.0", + "hash": "0x034c8cc69f22c35ae386a97136715dd48aaf97fd190942a111bfa680c2f2f421" + }, + { + "version": "0.2.0", + "hash": "0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808" + }, + { + "version": "0.1.0", + "hash": "0x038942ec840131a63c49fa514a3f0577ae401fd5584d56ad50cdf5a8b41d4538" + }, + { + "version": "0.0.1", + "hash": "0x03babef4b4c6d866d56e6356d961839fd9475931d11e0ea507420a87b0cadbdd" + } +] diff --git a/op-program/prestates/releases_test.go b/op-program/prestates/releases_test.go new file mode 100644 index 0000000000000..ae35a1297f767 --- /dev/null +++ b/op-program/prestates/releases_test.go @@ -0,0 +1,21 @@ +package prestates + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetReleases(t *testing.T) { + releases, err := GetReleases() + require.NoError(t, err, "expected no error while parsing embedded releases.json") + + foundGovernanceApproved := false + for _, release := range releases { + if release.GovernanceApproved { + foundGovernanceApproved = true + break + } + } + require.True(t, foundGovernanceApproved, "expected to find at least one GovernanceApproved release") +} diff --git a/op-program/prestates/verify/verify.go b/op-program/prestates/verify/verify.go new file mode 100644 index 0000000000000..a80bf2203e8ea --- /dev/null +++ b/op-program/prestates/verify/verify.go @@ -0,0 +1,86 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "slices" + + "github.com/ethereum-optimism/optimism/op-program/prestates" +) + +func main() { + var inputFile string + flag.StringVar(&inputFile, "input", "", "Releases JSON file to verify") + flag.Parse() + if inputFile == "" { + _, _ = fmt.Fprintln(os.Stderr, "Must specify --input") + os.Exit(2) + } + + in, err := os.OpenFile(inputFile, os.O_RDONLY, 0o644) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to open input file: %v\n", err.Error()) + os.Exit(2) + } + defer in.Close() + + input, err := os.ReadFile(inputFile) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to read input file: %v\n", err.Error()) + os.Exit(2) + } + var actual []prestates.Release + err = json.Unmarshal(input, &actual) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to parse JSON: %v\n", err.Error()) + os.Exit(2) + } + + expected, err := prestates.GetReleases() + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to load expected releases: %v\n", err.Error()) + os.Exit(2) + } + + sortFunc := func(a, b prestates.Release) int { + if a.Version > b.Version { + return 1 + } else if a.Version == b.Version { + return 0 + } + return -1 + } + slices.SortFunc(actual, sortFunc) + slices.SortFunc(expected, sortFunc) + + differs := false + report := "" + for i := 0; i < max(len(actual), len(expected)); i++ { + get := func(arr []prestates.Release, idx int) string { + if i >= len(arr) { + return "" + } else { + return formatRelease(arr[i]) + } + } + expectedStr := get(expected, i) + actualStr := get(actual, i) + releaseDiffers := expectedStr != actualStr + marker := "✅" + if releaseDiffers { + marker = "❌" + } + report += fmt.Sprintf("%v %d\tExpected: %v\tActual: %v\n", marker, i, expectedStr, actualStr) + differs = differs || releaseDiffers + } + fmt.Println(report) + if differs { + os.Exit(1) + } +} + +func formatRelease(release prestates.Release) string { + return fmt.Sprintf("%-13v %s", release.Version, release.Hash) +} diff --git a/op-program/scripts/build-prestates.sh b/op-program/scripts/build-prestates.sh index 0c0da57dbdd9e..a02984ae4d6cf 100755 --- a/op-program/scripts/build-prestates.sh +++ b/op-program/scripts/build-prestates.sh @@ -18,16 +18,20 @@ STATES_DIR="${SCRIPTS_DIR}/../temp/states" LOGS_DIR="${SCRIPTS_DIR}/../temp/logs" REPO_DIR="${TMP_DIR}/optimism" BIN_DIR="${REPO_DIR}/op-program/bin/" +VERSIONS_FILE="${STATES_DIR}/versions.json" mkdir -p "${STATES_DIR}" "${LOGS_DIR}" + cd "${REPO_DIR}" -VERSIONS=$(git tag | grep 'op-program\/v') +VERSIONS_JSON="[]" +VERSIONS=$(git tag --list 'op-program/v*' --sort taggerdate) for VERSION in ${VERSIONS} do - LOG_FILE="${LOGS_DIR}/build-$(echo "${VERSION}" | cut -c 12-).txt" + SHORT_VERSION=$(echo "${VERSION}" | cut -c 13-) + LOG_FILE="${LOGS_DIR}/build-${SHORT_VERSION}.txt" echo "Building Version: ${VERSION} Logs: ${LOG_FILE}" git checkout "${VERSION}" > "${LOG_FILE}" 2>&1 rm -rf "${BIN_DIR}" @@ -39,7 +43,10 @@ do else cp "${BIN_DIR}/prestate.json" "${STATES_DIR}/${HASH}.json" fi + + VERSIONS_JSON=$(echo "${VERSIONS_JSON}" | jq ". += [{\"version\": \"${SHORT_VERSION}\", \"hash\": \"${HASH}\"}]") echo "Built ${VERSION}: ${HASH}" done +echo "${VERSIONS_JSON}" > "${VERSIONS_FILE}" echo "All prestates successfully built and available in ${STATES_DIR}" diff --git a/op-program/verify/devnet/cmd/devnet.go b/op-program/verify/devnet/cmd/devnet.go index f955debc5c0eb..4ec63bf50bc16 100644 --- a/op-program/verify/devnet/cmd/devnet.go +++ b/op-program/verify/devnet/cmd/devnet.go @@ -43,8 +43,8 @@ func main() { os.Exit(2) } - // Apply the custom configs by running op-program in the same process - runner, err := verify.NewRunner(l1RpcUrl, l1RpcKind, l1BeaconUrl, l2RpcUrl, dataDir, "", 901, true) + // Apply the custom configs by running op-program + runner, err := verify.NewRunner(l1RpcUrl, l1RpcKind, l1BeaconUrl, l2RpcUrl, dataDir, "901", 901, false) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Failed to create runner: %v\n", err.Error()) os.Exit(1) diff --git a/op-proposer/Makefile b/op-proposer/Makefile index 561d7d32f301b..3a036e6d5d59b 100644 --- a/op-proposer/Makefile +++ b/op-proposer/Makefile @@ -1,34 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -# Find the github tag that points to this commit. If none are found, set the version string to "untagged" -# Prioritizes release tag, if one exists, over tags suffixed with "-rc" -VERSION ?= $(shell tags=$$(git tag --points-at $(GITCOMMIT) | grep '^op-proposer/' | sed 's/op-proposer\///' | sort -V); \ - preferred_tag=$$(echo "$$tags" | grep -v -- '-rc' | tail -n 1); \ - if [ -z "$$preferred_tag" ]; then \ - if [ -z "$$tags" ]; then \ - echo "untagged"; \ - else \ - echo "$$tags" | tail -n 1; \ - fi \ - else \ - echo $$preferred_tag; \ - fi) +DEPRECATED_TARGETS := op-proposer clean test -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.Version=$(VERSION) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -op-proposer: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-proposer ./cmd - -clean: - rm bin/op-proposer - -test: - go test -v ./... - -.PHONY: \ - clean \ - op-proposer \ - test +include ../just/deprecated.mk diff --git a/op-proposer/README.md b/op-proposer/README.md new file mode 100644 index 0000000000000..56f086061e1d7 --- /dev/null +++ b/op-proposer/README.md @@ -0,0 +1,153 @@ +# `op-proposer` + +Issues: [monorepo](https://github.com/ethereum-optimism/optimism/issues?q=is%3Aissue%20state%3Aopen%20label%3AA-op-proposer) + +Pull requests: [monorepo](https://github.com/ethereum-optimism/optimism/pulls?q=is%3Aopen+is%3Apr+label%3AA-op-proposer) + +User docs: +- [Proposer Configuration docs] + +[Proposer Configuration docs]: https://docs.optimism.io/builders/chain-operators/configuration/proposer + +Specs: +- [`proposals.md`](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/proposals.md) +- [`withdrawals`](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/withdrawals.md) +- [`fault-proof/stage-one/bridge-integration.md`](https://github.com/ethereum-optimism/specs/blob/main/specs/fault-proof/stage-one/bridge-integration.md) + +The `op-proposer` is a light-weight service to automate output-root proposal transactions on regular interval. +Each proposal transaction submits a claim of the L2 state to L1. + +Chains with a pre-Fault-Proof deployment make proposal-transactions towards a pre-fault-proofs `OptimismPortal` deployment. + +Chains with permissioned or permissionless Fault Proofs make proposal-transactions to the `DisputeGameFactory`, +which instantiates claims (each claim being a new fault-proof "game"), +which can then be resolved for the proposals to persist. + +Withdrawals are authenticated against resolved proposals, +with an inclusion-proof of a withdrawn message (as registered in the L2 withdrawal-contract storage). + +## Quickstart + +```bash +go run ./op-proposer/cmd \ + --l1-eth-rpc http://l1:8545 \ + --rollup-rpc: http://op-node:8545 \ + --game-factory-address=changeme \ + --game-type=changeme +``` + +See [Proposer Configuration docs] for customization of the transaction-managent, +and usage of a remote signer to isolate the proposer secret key. + +On test networks, `--allow-non-finalized` may be used to make proposals sooner, to reduce test time. + +## Usage + +### Build from source + +```bash +make op-proposer + +./bin/op-proposer --help +``` + +### Run from source + +```bash +# from op-proposer dir: +go run ./cmd --help +``` + +### Build docker image + +See `op-proposer` docker-bake target. + +## Overview + + + +The op-proposer relays subjective `finalized` blocks (irreversible, as locally verified) +to L1 by constructing and submitting proposals. +The proposed claims can then be resolved, and used for withdrawals on L1. + +```mermaid +sequenceDiagram +autonumber + +participant portal as OptimismPortal (v2)
contract +participant challenger as op-challenger +participant claim as Fault Dispute Game
contract +participant dgf as Dispute Game Factory
contract on L1 +participant proposer as op-proposer +participant opnode as op-node +participant el as Execution Engine
(e.g. op-geth) + +proposer ->>opnode: query output-root +opnode ->>el: query block and withdrawals-root +el -->> opnode: return block and withdrawals-root +opnode -->> proposer: return output root +proposer ->> dgf: send claim +proposer ->> proposer: repeat with next claim +dgf ->> claim: create game contract +challenger ->> claim: resolve (or counter) claim +portal -->> claim: proveWithdrawalTransaction checks game state +``` + +The `op-proposer` itself is a light-weight loop to maintain this relay: +schedule when to propose, inspect what to propose, transact on L1 to proposer, and repeat. + +## Product + +### Optimization target + +The `op-proposer` code optimizes for simplicity. + +Proposals are few and far-between, commonly only at a 1 hour interval. +Proposal execution speed affects tests more than it does production, and thus not a primary optimization target. + +Most costs are made in the proposal contract execution, +not the operation of the op-proposer, and thus not the primary optimization concern. + +Proposals are critical to safety however, and simplicity is thus important to this service. + +### Vision + +The pre-fault-proof proposal functionality is effectively unused code, and may be removed in the near future. +Solutions for alternative proving systems are a work in progress. + +With the proposed withdrawals-root feature (see [Isthmus upgrade feature]), +the op-node will soon no longer have to query the storage separately +from the block-header that it constructs an output-root for. +This lowers the requirements to run a proposer, +since no archive-node is required anymore to determine the withdrawals-root. + +[Isthmus upgrade feature]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/isthmus/exec-engine.md#l2tol1messagepasser-storage-root-in-header + +Testing of this service may be further improved by decoupling the scheduling and processing. +Better encapsulated processing would lend itself better to [op-e2e](../op-e2e) action-tests. + + +## Design principles + + + +- Reuse the transaction-management: this is the most complicated part of the op-proposer, but is common with other services. +- Keep the proposal flow simple: given that we only expect one transaction per hour, + but the transaction is a critical claim, we have a strong preference for safety over liveness. + +## Failure modes + + + +While disabled by default, the op-proposer is capable of submitting proposals too eagerly. +A proposal for unfinalized L2 state that does not hold true later may result in an invalid claim on L1, +and thus in dispute-game penalties. + +Assuming finality, the op-proposer is only really subject to liveness failures: +- to L1 RPC failure (mitigated with redundancy in L1 RPC) +- local temporary failure, e.g. offline execution engine (mitigated with alerts) + or odd tx-inclusion situations (mitigated with fresh state upon restart). + +## Testing + +The `op-proposer` integration is covered in system `op-e2e` tests. diff --git a/op-proposer/cmd/main.go b/op-proposer/cmd/main.go index cbb21fb285163..e5096351fdd73 100644 --- a/op-proposer/cmd/main.go +++ b/op-proposer/cmd/main.go @@ -19,7 +19,7 @@ import ( ) var ( - Version = "v0.10.14" + Version = "v0.0.0" GitCommit = "" GitDate = "" ) diff --git a/op-proposer/justfile b/op-proposer/justfile new file mode 100644 index 0000000000000..08b1b7b73911b --- /dev/null +++ b/op-proposer/justfile @@ -0,0 +1,20 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X main.Version=" + VERSION + " " + \ + "") + "'" + +BINARY := "./bin/op-proposer" + +# Build op-proposer binary +op-proposer: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") diff --git a/op-service/Makefile b/op-service/Makefile index 0978529f9b8da..eeb03302985da 100644 --- a/op-service/Makefile +++ b/op-service/Makefile @@ -1,25 +1,3 @@ -# Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169 -ifeq ($(shell uname),Darwin) - FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic -endif +DEPRECATED_TARGETS := test generate-mocks fuzz -test: - go test -v ./... - -generate-mocks: - go generate ./... - -fuzz: - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzExecutionPayloadUnmarshal ./eth - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzExecutionPayloadMarshalUnmarshalV1 ./eth - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzExecutionPayloadMarshalUnmarshalV2 ./eth - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzExecutionPayloadMarshalUnmarshalV3 ./eth - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzOBP01 ./eth - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzEncodeDecodeBlob ./eth - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDetectNonBijectivity ./eth - go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzEncodeScalar ./eth - -.PHONY: \ - test \ - generate-mocks \ - fuzz +include ../just/deprecated.mk diff --git a/op-service/README.md b/op-service/README.md new file mode 100644 index 0000000000000..9f8bf03c54ccb --- /dev/null +++ b/op-service/README.md @@ -0,0 +1,75 @@ +# `op-service` + +Issues: [monorepo](https://github.com/ethereum-optimism/optimism/issues?q=is%3Aissue%20state%3Aopen%20label%3AA-op-service) + +Pull requests: [monorepo](https://github.com/ethereum-optimism/optimism/pulls?q=is%3Aopen+is%3Apr+label%3AA-op-service) + +`op-service` is a collection of Go utilities to build OP-Stack services with. + +```text +├── cliapp - Flag and lifecycle handling for a Urfave v2 CLI app. +├── client - RPC and HTTP client utils +├── clock - Clock interface, system clock, tickers, mock/test time utils +├── crypto - Cryptography utils, complements geth crypto package +├── ctxinterrupt - Blocking/Interrupt handling +├── dial - Dialing util functions for RPC clients +├── endpoint - Abstracts away type of RPC endpoint +├── enum - Utils to create enums +├── errutil - Utils to work with customized errors +├── eth - Common Ethereum data types and OP-Stack extension types +├── flags - Utils and flag types for CLI usage +├── httputil - Utils to create enhanced HTTP Server +├── ioutil - File utils, including atomic files and compression +├── jsonutil - JSON encoding/decoding utils +├── locks - Lock utils, like read-write wrapped types +├── log - Logging CLI and middleware utils +├── metrics - Metrics types, metering abstractions, server utils +├── oppprof - P-Prof CLI types and server setup +├── predeploys - OP-Stack predeploy definitions +├── queue - Generic queue implementation +├── retry - Function retry utils +├── rpc - RPC server utils +├── safego - Utils to make Go memory more safe +├── serialize - Binary serialization abstractions +├── signer - CLI flags and bindings to work with a remote signer +├── solabi - Utils to encode/decode Solidity ABI formatted data +├── sources - RPC client bindings +├── tasks - Err-group with panic handling +├── testlog - Test logger and log-capture utils for testing +├── testutils - Simplified Ethereum types, mock RPC bindings, utils for testing. +├── tls - CLI flags and utils to work with TLS connections +├── txmgr - Transaction manager: automated nonce, fee and confirmation handling. +└── *.go - Miscellaneous utils (soon to be deprecated / moved) +``` + +## Usage + +From `op-service` dir: +```bash +# Run Go tests +make test +# Run Go fuzz tests +make fuzz +``` + +## Product + +### Optimization target + +Provide solid reusable building blocks for all OP-Stack Go services. + +### Vision + +- Remove unused utilities: `op-service` itself needs to stay maintainable. +- Make all Go services consistent: `op-service` modules can be used to simplify and improve more Go services. + +## Design principles + +- Reduce boilerplate in Go services: provide service building utils ranging from CLI to testing. +- Protect devs from sharp edges in the Go std-lib: think of providing missing composition, + proper resource-closing, well set up network-binding, safe concurrency utils. + +## Testing + +Each op-service package has its own unit-testing. +More advanced utils, such as the transaction manager, are covered in `op-e2e` as well. diff --git a/op-service/client/client.go b/op-service/client/client.go index 22f71866926b7..9f4dac659424e 100644 --- a/op-service/client/client.go +++ b/op-service/client/client.go @@ -79,43 +79,43 @@ func (ic *InstrumentedClient) RPC() RPC { } func (ic *InstrumentedClient) ChainID(ctx context.Context) (*big.Int, error) { - return instrument2[*big.Int](ic.m, "eth_chainId", func() (*big.Int, error) { + return instrument2(ic.m, "eth_chainId", func() (*big.Int, error) { return ic.c.ChainID(ctx) }) } func (ic *InstrumentedClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return instrument2[*types.Block](ic.m, "eth_getBlockByHash", func() (*types.Block, error) { + return instrument2(ic.m, "eth_getBlockByHash", func() (*types.Block, error) { return ic.c.BlockByHash(ctx, hash) }) } func (ic *InstrumentedClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - return instrument2[*types.Block](ic.m, "eth_getBlockByNumber", func() (*types.Block, error) { + return instrument2(ic.m, "eth_getBlockByNumber", func() (*types.Block, error) { return ic.c.BlockByNumber(ctx, number) }) } func (ic *InstrumentedClient) BlockNumber(ctx context.Context) (uint64, error) { - return instrument2[uint64](ic.m, "eth_blockNumber", func() (uint64, error) { + return instrument2(ic.m, "eth_blockNumber", func() (uint64, error) { return ic.c.BlockNumber(ctx) }) } func (ic *InstrumentedClient) PeerCount(ctx context.Context) (uint64, error) { - return instrument2[uint64](ic.m, "net_peerCount", func() (uint64, error) { + return instrument2(ic.m, "net_peerCount", func() (uint64, error) { return ic.c.PeerCount(ctx) }) } func (ic *InstrumentedClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - return instrument2[*types.Header](ic.m, "eth_getHeaderByHash", func() (*types.Header, error) { + return instrument2(ic.m, "eth_getHeaderByHash", func() (*types.Header, error) { return ic.c.HeaderByHash(ctx, hash) }) } func (ic *InstrumentedClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { - return instrument2[*types.Header](ic.m, "eth_getHeaderByNumber", func() (*types.Header, error) { + return instrument2(ic.m, "eth_getHeaderByNumber", func() (*types.Header, error) { return ic.c.HeaderByNumber(ctx, number) }) } @@ -132,25 +132,25 @@ func (ic *InstrumentedClient) TransactionSender(ctx context.Context, tx *types.T } func (ic *InstrumentedClient) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { - return instrument2[uint](ic.m, "eth_getTransactionCount", func() (uint, error) { + return instrument2(ic.m, "eth_getTransactionCount", func() (uint, error) { return ic.c.TransactionCount(ctx, blockHash) }) } func (ic *InstrumentedClient) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { - return instrument2[*types.Transaction](ic.m, "eth_getTransactionByBlockHashAndIndex", func() (*types.Transaction, error) { + return instrument2(ic.m, "eth_getTransactionByBlockHashAndIndex", func() (*types.Transaction, error) { return ic.c.TransactionInBlock(ctx, blockHash, index) }) } func (ic *InstrumentedClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { - return instrument2[*types.Receipt](ic.m, "eth_getTransactionReceipt", func() (*types.Receipt, error) { + return instrument2(ic.m, "eth_getTransactionReceipt", func() (*types.Receipt, error) { return ic.c.TransactionReceipt(ctx, txHash) }) } func (ic *InstrumentedClient) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { - return instrument2[*ethereum.SyncProgress](ic.m, "eth_syncing", func() (*ethereum.SyncProgress, error) { + return instrument2(ic.m, "eth_syncing", func() (*ethereum.SyncProgress, error) { return ic.c.SyncProgress(ctx) }) } @@ -160,37 +160,37 @@ func (ic *InstrumentedClient) SubscribeNewHead(ctx context.Context, ch chan<- *t } func (ic *InstrumentedClient) NetworkID(ctx context.Context) (*big.Int, error) { - return instrument2[*big.Int](ic.m, "net_version", func() (*big.Int, error) { + return instrument2(ic.m, "net_version", func() (*big.Int, error) { return ic.c.NetworkID(ctx) }) } func (ic *InstrumentedClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { - return instrument2[*big.Int](ic.m, "eth_getBalance", func() (*big.Int, error) { + return instrument2(ic.m, "eth_getBalance", func() (*big.Int, error) { return ic.c.BalanceAt(ctx, account, blockNumber) }) } func (ic *InstrumentedClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { - return instrument2[[]byte](ic.m, "eth_getStorageAt", func() ([]byte, error) { + return instrument2(ic.m, "eth_getStorageAt", func() ([]byte, error) { return ic.c.StorageAt(ctx, account, key, blockNumber) }) } func (ic *InstrumentedClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { - return instrument2[[]byte](ic.m, "eth_getCode", func() ([]byte, error) { + return instrument2(ic.m, "eth_getCode", func() ([]byte, error) { return ic.c.CodeAt(ctx, account, blockNumber) }) } func (ic *InstrumentedClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { - return instrument2[uint64](ic.m, "eth_getTransactionCount", func() (uint64, error) { + return instrument2(ic.m, "eth_getTransactionCount", func() (uint64, error) { return ic.c.NonceAt(ctx, account, blockNumber) }) } func (ic *InstrumentedClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { - return instrument2[[]types.Log](ic.m, "eth_getLogs", func() ([]types.Log, error) { + return instrument2(ic.m, "eth_getLogs", func() ([]types.Log, error) { return ic.c.FilterLogs(ctx, q) }) } @@ -200,67 +200,67 @@ func (ic *InstrumentedClient) SubscribeFilterLogs(ctx context.Context, q ethereu } func (ic *InstrumentedClient) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { - return instrument2[*big.Int](ic.m, "eth_getBalance", func() (*big.Int, error) { + return instrument2(ic.m, "eth_getBalance", func() (*big.Int, error) { return ic.c.PendingBalanceAt(ctx, account) }) } func (ic *InstrumentedClient) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { - return instrument2[[]byte](ic.m, "eth_getStorageAt", func() ([]byte, error) { + return instrument2(ic.m, "eth_getStorageAt", func() ([]byte, error) { return ic.c.PendingStorageAt(ctx, account, key) }) } func (ic *InstrumentedClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { - return instrument2[[]byte](ic.m, "eth_getCode", func() ([]byte, error) { + return instrument2(ic.m, "eth_getCode", func() ([]byte, error) { return ic.c.PendingCodeAt(ctx, account) }) } func (ic *InstrumentedClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - return instrument2[uint64](ic.m, "eth_getTransactionCount", func() (uint64, error) { + return instrument2(ic.m, "eth_getTransactionCount", func() (uint64, error) { return ic.c.PendingNonceAt(ctx, account) }) } func (ic *InstrumentedClient) PendingTransactionCount(ctx context.Context) (uint, error) { - return instrument2[uint](ic.m, "eth_getBlockTransactionCountByNumber", func() (uint, error) { + return instrument2(ic.m, "eth_getBlockTransactionCountByNumber", func() (uint, error) { return ic.c.PendingTransactionCount(ctx) }) } func (ic *InstrumentedClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - return instrument2[[]byte](ic.m, "eth_call", func() ([]byte, error) { + return instrument2(ic.m, "eth_call", func() ([]byte, error) { return ic.c.CallContract(ctx, msg, blockNumber) }) } func (ic *InstrumentedClient) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { - return instrument2[[]byte](ic.m, "eth_call", func() ([]byte, error) { + return instrument2(ic.m, "eth_call", func() ([]byte, error) { return ic.c.CallContractAtHash(ctx, msg, blockHash) }) } func (ic *InstrumentedClient) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { - return instrument2[[]byte](ic.m, "eth_call", func() ([]byte, error) { + return instrument2(ic.m, "eth_call", func() ([]byte, error) { return ic.c.PendingCallContract(ctx, msg) }) } func (ic *InstrumentedClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - return instrument2[*big.Int](ic.m, "eth_gasPrice", func() (*big.Int, error) { + return instrument2(ic.m, "eth_gasPrice", func() (*big.Int, error) { return ic.c.SuggestGasPrice(ctx) }) } func (ic *InstrumentedClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return instrument2[*big.Int](ic.m, "eth_maxPriorityFeePerGas", func() (*big.Int, error) { + return instrument2(ic.m, "eth_maxPriorityFeePerGas", func() (*big.Int, error) { return ic.c.SuggestGasPrice(ctx) }) } func (ic *InstrumentedClient) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { - return instrument2[uint64](ic.m, "eth_estimateGas", func() (uint64, error) { + return instrument2(ic.m, "eth_estimateGas", func() (uint64, error) { return ic.c.EstimateGas(ctx, msg) }) } diff --git a/op-service/client/lazy_dial.go b/op-service/client/lazy_dial.go index 9064fbe1fe09d..606bfac8f9188 100644 --- a/op-service/client/lazy_dial.go +++ b/op-service/client/lazy_dial.go @@ -10,33 +10,33 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -// LazyRPC defers connection attempts to the usage of the RPC. +// lazyRPC defers connection attempts to the usage of the RPC. // This allows a websocket connection to be established lazily. // The underlying RPC should handle reconnects. -type LazyRPC struct { +type lazyRPC struct { // mutex to prevent more than one active dial attempt at a time. mu sync.Mutex // inner is the actual RPC client. // It is initialized once. The underlying RPC handles reconnections. inner RPC // options to initialize `inner` with. - opts []rpc.ClientOption + cfg rpcConfig endpoint string // If we have not initialized `inner` yet, // do not try to do so after closing the client. closed bool } -var _ RPC = (*LazyRPC)(nil) +var _ RPC = (*lazyRPC)(nil) -func NewLazyRPC(endpoint string, opts ...rpc.ClientOption) *LazyRPC { - return &LazyRPC{ - opts: opts, +func newLazyRPC(endpoint string, cfg rpcConfig) *lazyRPC { + return &lazyRPC{ + cfg: cfg, endpoint: endpoint, } } -func (l *LazyRPC) dial(ctx context.Context) error { +func (l *lazyRPC) dial(ctx context.Context) error { l.mu.Lock() defer l.mu.Unlock() if l.inner != nil { @@ -45,15 +45,15 @@ func (l *LazyRPC) dial(ctx context.Context) error { if l.closed { return errors.New("cannot dial RPC, client was already closed") } - underlying, err := rpc.DialOptions(ctx, l.endpoint, l.opts...) + underlying, err := rpc.DialOptions(ctx, l.endpoint, l.cfg.gethRPCOptions...) if err != nil { return fmt.Errorf("failed to dial: %w", err) } - l.inner = NewBaseRPCClient(underlying) + l.inner = wrapClient(underlying, l.cfg) return nil } -func (l *LazyRPC) Close() { +func (l *lazyRPC) Close() { l.mu.Lock() defer l.mu.Unlock() if l.inner != nil { @@ -62,21 +62,21 @@ func (l *LazyRPC) Close() { l.closed = true } -func (l *LazyRPC) CallContext(ctx context.Context, result any, method string, args ...any) error { +func (l *lazyRPC) CallContext(ctx context.Context, result any, method string, args ...any) error { if err := l.dial(ctx); err != nil { return err } return l.inner.CallContext(ctx, result, method, args...) } -func (l *LazyRPC) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { +func (l *lazyRPC) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { if err := l.dial(ctx); err != nil { return err } return l.inner.BatchCallContext(ctx, b) } -func (l *LazyRPC) EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error) { +func (l *lazyRPC) EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error) { if err := l.dial(ctx); err != nil { return nil, err } diff --git a/op-service/client/lazy_dial_test.go b/op-service/client/lazy_dial_test.go index 79608dc2b236f..7af05416e5fb1 100644 --- a/op-service/client/lazy_dial_test.go +++ b/op-service/client/lazy_dial_test.go @@ -28,7 +28,7 @@ func TestLazyRPC(t *testing.T) { addr := listener.Addr().String() - cl := NewLazyRPC("ws://" + addr) + cl := newLazyRPC("ws://"+addr, applyOptions(nil)) defer cl.Close() // At this point the connection is online, but the RPC is not. diff --git a/op-service/client/rpc.go b/op-service/client/rpc.go index 8fb2d4d37b886..c37a4a53dd0e8 100644 --- a/op-service/client/rpc.go +++ b/op-service/client/rpc.go @@ -8,9 +8,8 @@ import ( "regexp" "time" - "golang.org/x/time/rate" - "github.com/prometheus/client_golang/prometheus" + "golang.org/x/time/rate" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/log" @@ -38,77 +37,92 @@ type rpcConfig struct { lazy bool callTimeout time.Duration batchCallTimeout time.Duration + fixedDialBackoff time.Duration } -type RPCOption func(cfg *rpcConfig) error +type RPCOption func(cfg *rpcConfig) func WithCallTimeout(d time.Duration) RPCOption { - return func(cfg *rpcConfig) error { + return func(cfg *rpcConfig) { cfg.callTimeout = d - return nil } } func WithBatchCallTimeout(d time.Duration) RPCOption { - return func(cfg *rpcConfig) error { + return func(cfg *rpcConfig) { cfg.batchCallTimeout = d - return nil } } -// WithDialBackoff configures the number of attempts for the initial dial to the RPC, -// attempts are executed with an exponential backoff strategy. -func WithDialBackoff(attempts int) RPCOption { - return func(cfg *rpcConfig) error { +// WithDialAttempts configures the number of attempts for the initial dial to the RPC, +// attempts are executed with an exponential backoff strategy by default. +func WithDialAttempts(attempts int) RPCOption { + return func(cfg *rpcConfig) { cfg.backoffAttempts = attempts - return nil + } +} + +// WithFixedDialBackoff makes the RPC client use a fixed delay between dial attempts of 2 seconds instead of exponential +func WithFixedDialBackoff(d time.Duration) RPCOption { + return func(cfg *rpcConfig) { + cfg.fixedDialBackoff = d } } // WithHttpPollInterval configures the RPC to poll at the given rate, in case RPC subscriptions are not available. func WithHttpPollInterval(duration time.Duration) RPCOption { - return func(cfg *rpcConfig) error { + return func(cfg *rpcConfig) { cfg.httpPollInterval = duration - return nil } } // WithGethRPCOptions passes the list of go-ethereum RPC options to the internal RPC instance. func WithGethRPCOptions(gethRPCOptions ...rpc.ClientOption) RPCOption { - return func(cfg *rpcConfig) error { + return func(cfg *rpcConfig) { cfg.gethRPCOptions = append(cfg.gethRPCOptions, gethRPCOptions...) - return nil } } // WithRateLimit configures the RPC to target the given rate limit (in requests / second). // See NewRateLimitingClient for more details. func WithRateLimit(rateLimit float64, burst int) RPCOption { - return func(cfg *rpcConfig) error { + return func(cfg *rpcConfig) { cfg.limit = rateLimit cfg.burst = burst - return nil } } // WithLazyDial makes the RPC client initialization defer the initial connection attempt, // and defer to later RPC requests upon subsequent dial errors. // Any dial-backoff option will be ignored if this option is used. -// This is implemented by wrapping the inner RPC client with a LazyRPC. func WithLazyDial() RPCOption { - return func(cfg *rpcConfig) error { + return func(cfg *rpcConfig) { cfg.lazy = true - return nil } } // NewRPC returns the correct client.RPC instance for a given RPC url. func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...RPCOption) (RPC, error) { - var cfg rpcConfig - for i, opt := range opts { - if err := opt(&cfg); err != nil { - return nil, fmt.Errorf("rpc option %d failed to apply to RPC config: %w", i, err) + cfg := applyOptions(opts) + + var wrapped RPC + if cfg.lazy { + wrapped = newLazyRPC(addr, cfg) + } else { + underlying, err := dialRPCClientWithBackoff(ctx, lgr, addr, cfg) + if err != nil { + return nil, err } + wrapped = wrapClient(underlying, cfg) + } + + return NewRPCWithClient(ctx, lgr, addr, wrapped, cfg.httpPollInterval) +} + +func applyOptions(opts []RPCOption) rpcConfig { + var cfg rpcConfig + for _, opt := range opts { + opt(&cfg) } if cfg.backoffAttempts < 1 { // default to at least 1 attempt, or it always fails to dial. @@ -120,23 +134,7 @@ func NewRPC(ctx context.Context, lgr log.Logger, addr string, opts ...RPCOption) if cfg.batchCallTimeout == 0 { cfg.batchCallTimeout = 20 * time.Second } - - var wrapped RPC - if cfg.lazy { - wrapped = NewLazyRPC(addr, cfg.gethRPCOptions...) - } else { - underlying, err := dialRPCClientWithBackoff(ctx, lgr, addr, cfg.backoffAttempts, cfg.gethRPCOptions...) - if err != nil { - return nil, err - } - wrapped = &BaseRPCClient{c: underlying, callTimeout: cfg.callTimeout, batchCallTimeout: cfg.batchCallTimeout} - } - - if cfg.limit != 0 { - wrapped = NewRateLimitingClient(wrapped, rate.Limit(cfg.limit), cfg.burst) - } - - return NewRPCWithClient(ctx, lgr, addr, wrapped, cfg.httpPollInterval) + return cfg } // NewRPCWithClient builds a new polling client with the given underlying RPC client. @@ -148,14 +146,17 @@ func NewRPCWithClient(ctx context.Context, lgr log.Logger, addr string, underlyi } // Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional. -func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string, attempts int, opts ...rpc.ClientOption) (*rpc.Client, error) { +func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string, cfg rpcConfig) (*rpc.Client, error) { bOff := retry.Exponential() - return retry.Do(ctx, attempts, bOff, func() (*rpc.Client, error) { + if cfg.fixedDialBackoff != 0 { + bOff = retry.Fixed(cfg.fixedDialBackoff) + } + return retry.Do(ctx, cfg.backoffAttempts, bOff, func() (*rpc.Client, error) { if !IsURLAvailable(ctx, addr) { log.Warn("failed to dial address, but may connect later", "addr", addr) return nil, fmt.Errorf("address unavailable (%s)", addr) } - client, err := rpc.DialOptions(ctx, addr, opts...) + client, err := rpc.DialOptions(ctx, addr, cfg.gethRPCOptions...) if err != nil { return nil, fmt.Errorf("failed to dial address (%s): %w", addr, err) } @@ -191,15 +192,26 @@ func IsURLAvailable(ctx context.Context, address string) bool { // BaseRPCClient is a wrapper around a concrete *rpc.Client instance to make it compliant // with the client.RPC interface. -// It sets a timeout of 10s on CallContext & 20s on BatchCallContext made through it. +// It sets a default timeout of 10s on CallContext & 20s on BatchCallContext made through it. type BaseRPCClient struct { c *rpc.Client batchCallTimeout time.Duration callTimeout time.Duration } -func NewBaseRPCClient(c *rpc.Client) *BaseRPCClient { - return &BaseRPCClient{c: c, callTimeout: 10 * time.Second, batchCallTimeout: 20 * time.Second} +func NewBaseRPCClient(c *rpc.Client, opts ...RPCOption) RPC { + cfg := applyOptions(opts) + return wrapClient(c, cfg) +} + +func wrapClient(c *rpc.Client, cfg rpcConfig) RPC { + var wrapped RPC + wrapped = &BaseRPCClient{c: c, callTimeout: cfg.callTimeout, batchCallTimeout: cfg.batchCallTimeout} + + if cfg.limit != 0 { + wrapped = NewRateLimitingClient(wrapped, rate.Limit(cfg.limit), cfg.burst) + } + return wrapped } func (b *BaseRPCClient) Close() { diff --git a/op-service/dial/dial.go b/op-service/dial/dial.go index 4cf78f84fd21d..ee7ca35e5882e 100644 --- a/op-service/dial/dial.go +++ b/op-service/dial/dial.go @@ -35,16 +35,22 @@ func DialEthClientWithTimeout(ctx context.Context, timeout time.Duration, log lo // DialRollupClientWithTimeout attempts to dial the RPC provider using the provided URL. // If the dial doesn't complete within timeout seconds, this method will return an error. -func DialRollupClientWithTimeout(ctx context.Context, timeout time.Duration, log log.Logger, url string) (*sources.RollupClient, error) { +func DialRollupClientWithTimeout(ctx context.Context, timeout time.Duration, log log.Logger, url string, callerOpts ...client.RPCOption) (*sources.RollupClient, error) { ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - rpcCl, err := dialRPCClientWithBackoff(ctx, log, url) + opts := []client.RPCOption{ + client.WithFixedDialBackoff(defaultRetryTime), + client.WithDialAttempts(defaultRetryCount), + } + opts = append(opts, callerOpts...) + + rpcCl, err := client.NewRPC(ctx, log, url, opts...) if err != nil { return nil, err } - return sources.NewRollupClient(client.NewBaseRPCClient(rpcCl)), nil + return sources.NewRollupClient(rpcCl), nil } // DialRPCClientWithTimeout attempts to dial the RPC provider using the provided URL. diff --git a/op-service/dial/ethclient_interface.go b/op-service/dial/ethclient_interface.go index 58a2070974eeb..292c7b07fa4c6 100644 --- a/op-service/dial/ethclient_interface.go +++ b/op-service/dial/ethclient_interface.go @@ -5,12 +5,14 @@ import ( "math/big" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" ) // EthClientInterface is an interface for providing an ethclient.Client // It does not describe all of the functions an ethclient.Client has, only the ones used by callers of the L2 Providers type EthClientInterface interface { BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) + Client() *rpc.Client Close() } diff --git a/op-service/eth/account_proof.go b/op-service/eth/account_proof.go index b7d09eeff0629..4ddc3eff156bb 100644 --- a/op-service/eth/account_proof.go +++ b/op-service/eth/account_proof.go @@ -13,7 +13,7 @@ import ( ) type StorageProofEntry struct { - Key common.Hash `json:"key"` + Key hexutil.Big `json:"key"` Value hexutil.Big `json:"value"` Proof []hexutil.Bytes `json:"proof"` } @@ -46,20 +46,20 @@ func (res *AccountResult) Verify(stateRoot common.Hash) error { return fmt.Errorf("failed to load storage proof node %d of storage value %d into mem db: %w", j, i, err) } } - path := crypto.Keccak256(entry.Key[:]) + path := crypto.Keccak256(entry.Key.ToInt().FillBytes(make([]byte, 32))) val, err := trie.VerifyProof(res.StorageHash, path, db) if err != nil { - return fmt.Errorf("failed to verify storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key, path, res.StorageHash, err) + return fmt.Errorf("failed to verify storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key.String(), path, res.StorageHash, err) } if val == nil && entry.Value.ToInt().Cmp(common.Big0) == 0 { // empty storage is zero by default continue } comparison, err := rlp.EncodeToBytes(entry.Value.ToInt().Bytes()) if err != nil { - return fmt.Errorf("failed to encode storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key, path, res.StorageHash, err) + return fmt.Errorf("failed to encode storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key.String(), path, res.StorageHash, err) } if !bytes.Equal(val, comparison) { - return fmt.Errorf("value %d in storage proof does not match proven value at key %s (path %x)", i, entry.Key, path) + return fmt.Errorf("value %d in storage proof does not match proven value at key %s (path %x)", i, entry.Key.String(), path) } } diff --git a/op-service/eth/account_proof_test.go b/op-service/eth/account_proof_test.go index 051023e8e9af8..210aebc513b77 100644 --- a/op-service/eth/account_proof_test.go +++ b/op-service/eth/account_proof_test.go @@ -44,6 +44,37 @@ const resultData = ` } ` +// Truncated storage proof key to test unmarshalling. +const invalidResultDataTruncated = ` +{ + "address": "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "balance": "0x0", + "codeHash": "0x1f958654ab06a152993e7a0ae7b6dbb0d4b19265cc9337b8789fe1353bd9dc35", + "nonce": "0x1", + "storageHash": "0x88219055c2fef8800e02f071d053a86a4194e70a81b6e45f1fecca7dae0432da", + "accountProof": [ + "0xf90211a063a66cd84a54f8ee248662f1d4637936c430a0f455eeec8c01ee56db898dddfba0be9003fb3e36a55cfea1eda010c0a459f10729db9809e0bd1e3599f46c5ffed1a0a08d018d3cf38b0d0cbff14288699705dfa7cf27dc20fbbaae9351837eff4751a0eed877086740a930f035b75ebb26ce63df0f61baea52bf05f4c7421014debf33a053ea34e49423e790b10d9a36f498f337b3f079ed611d98a3f8550c34212dcbd7a0c370d5b874f70b9fd1c8a2fe98b0ef60c480fbe00566a7d5a5e682d9859398f2a0da820e94aac0b444a8dcfebc7dc9ec942f04f252da25b10faf50b57f969aa1f5a0413e8039c67d8acbe20993ab364c2c477d1ce85e8ae723c33acd506175ce4bffa0f70e5d5d934c53b2302ec3f98bd3f33f39a15fabb8c32e5e7acc97121d7a9cf3a0b41e7073ae943e498681b5d86941401c29b38c93fa347ace6bb15ba74ccbf45ea0a3b0aa548cac9cbbfcfabd980c1ceae8bdc39ad2682fc6e6d9cf0f4bdb273884a04d7932870a3d25163ea28ae5ebe702b841d755541d2af98c5c1c08090327fab1a06e41c3fb6362dd860a098aacf13a81c9d26e9b822c1066ca76cb98607f3e257aa0079ffe59ddb21ccd03bcbf1cc42fc0fb89dcae93ffeed9b82a848828199ab057a0dce67e92c8991df57ecac2237244d12e92f6514db1c5f076718fe40266bbf741a08dd7d3b3b041889f837217761b4e87510428ea41b3aff4e5725fd8efc2d735b980", + "0xf90211a0809683f3310d75dff5eb95296aa9ff5d74fbde9f873b9a6b245513887f9c6e91a055450f5338cc2f8f4306912e938df3fe490929614604eeea4c03581b98c8ae8ea04e50b57da8fc16a5d5460892196631737eeb1cc1e995e5c1de9c381ed1fb84d4a07d65e61a50579d689422446c23df10c4c0b5ec41239a910ca86634e2fee75320a091c77e1f72302bdb3985b249dba07d1abaa345296080c369bd84c518669297e1a019a185bedc83ab48c51dffe4c58ab88e30c88976a3b059ab524ef7ab42886d61a0a6c249e070db991141ee1289a5ed212f81673f8cd3f7bf35c27c335cc77d3eeca0c7d7a7f5036c8c3185cd0ca231775047192419b8f7e7b5a462c8e713ab2f4fcda006084fdd6777d076850defc5c6f1336535bbc2ec95a0e3f91fc5ac9761aee770a0c85a82f527990667217fac36ebfb9f4af29a6ff7b0b3d41cdcb256a26ca5f621a06a382d1f5a9bb0b712c89e82b0aaf26cf7c5984255377fd7428457d390330d40a0194f1f730e71559662ea2d9bdc681761eaf54decc7041766b5d7b7e8086d2480a05afe23c9ec57c22d9639f9228aa389e7a70a4e1e3e675856792f4a92fe284478a05bcacd2d3d2ac267d5b0367b56f05e4c808e2a5ecd04a10f1399e313fd41b273a09e62b6f5b7b77a1657ded9f0bef2af7fee11f2bf0518a5cceb5ceae2845c16f0a06d0ee25c5a3acd2b8d3253b856a77187b76f90d60b2356fc77f6e79766410cc580", + "0xf90211a0a6b81aae9b8aff6ac275885f6dfa4bc11949e3e8cbfad05714c3233303fa83f5a0e29595c647574b219c3068a768d47347b0e8a272da881aeb4525af051faab847a0441c1549c250c0c1bc0fa1b73e9f9ac9998b5dcef65a57ecd3f748ce02be4251a0353bd042ac0cf9a90a9cc02cc131f5d58f531df8df7ab752f6caa9b6807a506ea07340f489ba55fc8cfde61384c4990f74034f0bc0c7e1d68733284cb5c30d5bbea00ff5d4191ef973be9ae73b3fd9d01f52b54aafa20f147b6a5ca6b9e56a1f9ec4a0e167cd5a249a0dc2afbb9b2aafbd3b6e0160739a99e482d22d722c78fa296772a004202f2695770715d36e9aad418cc005fd8b22b927f1e1383b4e95ca18f41f61a0be38b6340286e0cd2454d90d8ed2f7e26bce5b7774f8adfa8f54a75bc4635d18a0cacc635e487a0d7dd19373bcd0a32e4cea0655f93d61f2940a6063059a044bf7a0bcd8f9ab88356e86cea7cd27454525ade016bccf26f414ad9fa93e0280d40df4a0d5651902739f9dfaff0f1178ea7cba617087234dd0e2895424961fad98605a27a0f76890befb5b3b20695d64b6a7c416709c93032012b46245c5bc00dd104b84f3a00ff372b11e0fb8febd467e060f7ce126e705a07a203a3f6dd93c7e3f36f4608ea0b4ea8133548c9b9d8f62b86aa703f65e3323a92a4b4711f80a734b80814b0825a04db29c4cb760e4831bfe40cdb0f554d74e98da26715c7e6319317c8c9a9c247580", + "0xf90211a026ffcc82ed6e3cd13ea30ed185afae29eed7f7fbde7f46010061791b5441b7dfa086b3018a2c001ffd6cc76e58372c49f5a2ba42335789fdcea878d93ceeeeb969a0589ba5e683afa655b17eb6b6c687a657669f772b1a2f78813ea662e8c316c12ea01c604e2e2f9ace5ef281f09c4b6c24c4c4631810f30b5209a433515a628cb5aca0520abee45bbc79e9f9519ffd4ad199b40383cb9718a3e8392d7193f68b1bc251a0b788e74186f121dd5ad31ef6b69d69147ab1841aa5380928fbe11a65ad67af36a0ef80a7fd5edf9901e2d8fa0cd8d9608e9fde114da1bd0f545e107c6771d5b0e7a05e8d9b24b83dbb8ec946cd42ff04bd0588f15866cd95095a8495242616b9ae71a0d623ee5bd0f3b8513ad7c247d1736841878f7210445209cecf36f0bfa5b8a6b9a03d0b62b3dc96b9c72190ff3484699d4892dea93cd16d9811cd58bd614348db11a0b140f98169be15dc1266be9343a1225fe6339f86e309854b03af9d304e75bd76a04ca100367dd9f12a6e80f48a1fabc19d9d36f07960d1911c3a09199a43eb26d2a05e9c627adafc5393a9b5ddc910f6474c56a10366f9d44248d9c0ce2e0c6b9a94a097e533731c36c43d7cf20379f2349ac1cd7a1165fb3588432be8d315801b2e80a0765168ad98f52483060045ae5208451078b2e6876a6f90d40a5c3e3f31cc559ba0479dd4f67d939fa21dd0528703a68c933f8a3d8e504d48f8c9bf7c41e92deecd80", + "0xf90211a04232cef0e6c4bbd5969f864233a23762543460900e04868931685e0148ae2d10a05353ae18ba63650d7281fefa6fb545b7314cadafd459eed25c7db4915d834e95a022fe8bbf3b304ea8fa6e0cb69c9a3a05cdcf0c3542a5e389a9518177a1925bdca0377ac9d4284000e1f98327783989043f4a6b59d48f5a80579c71adfd880f651ea049da166e0ceb03cf24a2cc03b3bd5e862eddd540a2c517493125322b3a30e85ba0aa9980b3bf84ce0b360f10ca3b230b5dbc9eecba684ed1add96b23167728574ea0f28a3be0e42f13e78f306970fd3a1aac286b30af8af1f460e50eba1d879d61b8a0c84f2fd48976ee7662adc809abb439ea056b3615b622f2938b597782501a4279a0ca13452ffbe75eedde1d870340997ce269c83f6642eefa2d4e9d6bd21c8fc838a0dd918c25e25823548a6a31edb27b65421b2b77063cdc71b13c43eed15b86b924a01a4d8ab05ce030242b59014d96fe1adca52c3f5d13eb09feefbf6eaf97e6fcfba09187e247644a19fe62860dba6e2317f40fe9907c8101bf9e1b04e4b5dadb8ec4a02c299cdc9b87c7f3b1402627f9bcc488d8655a6cbc5d458155024dc8be90ea7aa0373f215d7bc10a74a8e11ddbd3395e27d55cfab62a433b2c6961c1beee9ff3c8a04ec09787d6040119700a0d38154d4a589e1d62245fcd685768cd265cda5ee576a00086a240676e913c0b969397fbc72191719834bc533ba4601406ea062ea76f9b80", + "0xf90151808080a0ae1018f6569474784bbb933125e397f72f160cb86bf9528ba522e2957e6b27b6a07e10da74c2d11b8dda5b0127b4b39a0d7a1f4a1c9f0dc1a05ae1f3fa3346c86ba0884fa49d5faae435667fe982950ccf82aa58a148dffdb99c5eb7da6b01fd9b00a0065e97ea5d45a492c2aa8eade7534551a04e7899f0bcebeeccc42a1cb2292ce3a0c3a2aae48ed7395cc59065eedd5cb40d9a0cb02db9a9afaccd27efd6282464eb808080a0fc9e1fdc7239d8adc047265bb6589ddefac9a63c1c9829ef2b4717a4b9000dd7a0c285558e316f3ea0ceb2ca5681a79e5d3e3d6d6f21054d5056a6e9ad7dcdd6c7a0de8e2f7f5743997eabe69cb1d99ef0aec670da0b31b466bd8e14d24df17542d6a026ad23a1ed5a6f66a4e6e64fa1b3c37c0878975ba0b8872f5d8ae7c215a0f9c5a0f0ac72c6fc609e78ca13cefea04ef39ff7c9c49198a641508bf7d51bc997239180", + "0xf851808080808080a0292e7aa7b0fa371f45a26562a180d952f2f3bd3d7a67eb019747b10876cd61a6a0c7f2b75df52f531ca04c4b7c6449bb8be8eae52bf543dfb78383eda4625d922e808080808080808080", + "0xf8669d37118893aaaf73153bacee2bbd50b8234ab255361cc8614a5713b77282b846f8440180a088219055c2fef8800e02f071d053a86a4194e70a81b6e45f1fecca7dae0432daa01f958654ab06a152993e7a0ae7b6dbb0d4b19265cc9337b8789fe1353bd9dc35" + ], + "storageProof": [ + { + "key": "0x0", + "proof": [ + "0xf901118080a04fc5f13ab2f9ba0c2da88b0151ab0e7cf4d85d08cca45ccd923c6ab76323eb28a09d1f77882a1c2e804de950478b4fdec793decb817e7bbe24a2afd23eb000d648a0f57febb7b16455e051f412a56e54016c676a3d4aa515d2e77a90520dfe36162ea0dce964c738816bb26d659513b793496cac2279d100812e6441aae3f7ffefce2080a0d5223d0cc181c8c0cd1babb8cd0b4d6433eab19a9fcc7836681589aad346556fa0c61ebce1cecbc190ee1163d0ff9ff456cb1fe3409dc546bf2f9118662e6db892a024513ee2bee3b30d4b4e4b600b5a98db38db03f6db556f492d24ac0ff9d6c98fa019bbead828fb8baf57dfda3a30a0b6da048e31faee39f5a76a99b51f28c6c512808080808080", + "0xf7a031a88f3936348d602f3078126bdcd162c575cb17fb9bbfe2dab00b167bd295c39594715b7219d986641df9efd9c7ef01218d528e19ec" + ], + "value": "0x715b7219d986641df9efd9c7ef01218d528e19ec" + } + ] +} +` + var goodRoot = common.HexToHash("0x070ef87d6d3a8a132dfb45cbbc86daf545a45f1a0263bd28a304e465327f3557") func TestAccountResult_Verify(t *testing.T) { @@ -58,10 +89,15 @@ func TestAccountResult_Verify(t *testing.T) { require.NotNil(t, result.Verify(goodRoot), "does not verify against bad proof") } +func TestAccountResult_MarshalTruncated(t *testing.T) { + var result AccountResult + require.NoError(t, json.Unmarshal([]byte(invalidResultDataTruncated), &result)) +} + func FuzzAccountResult_StorageProof(f *testing.F) { f.Fuzz(func(t *testing.T, key []byte, value []byte) { result := makeResult(t) - result.StorageProof[0].Key = common.BytesToHash(key) + result.StorageProof[0].Key = hexutil.Big(*common.BytesToHash(key).Big()) result.StorageProof[0].Value = hexutil.Big(*(new(big.Int).SetBytes(value))) require.NotNil(t, result.Verify(goodRoot), "does not verify against bad proof") }) diff --git a/op-service/eth/blobs_api.go b/op-service/eth/blobs_api.go index 3dbff1745bc23..239f97528edeb 100644 --- a/op-service/eth/blobs_api.go +++ b/op-service/eth/blobs_api.go @@ -1,5 +1,7 @@ package eth +import "github.com/ethereum/go-ethereum/common/hexutil" + type BlobSidecar struct { Blob Blob `json:"blob"` Index Uint64String `json:"index"` @@ -13,8 +15,7 @@ type APIBlobSidecar struct { KZGCommitment Bytes48 `json:"kzg_commitment"` KZGProof Bytes48 `json:"kzg_proof"` SignedBlockHeader SignedBeaconBlockHeader `json:"signed_block_header"` - // The inclusion-proof of the blob-sidecar into the beacon-block is ignored, - // since we verify blobs by their versioned hashes against the execution-layer block instead. + InclusionProof []Bytes32 `json:"kzg_commitment_inclusion_proof"` } func (sc *APIBlobSidecar) BlobSidecar() *BlobSidecar { @@ -27,8 +28,8 @@ func (sc *APIBlobSidecar) BlobSidecar() *BlobSidecar { } type SignedBeaconBlockHeader struct { - Message BeaconBlockHeader `json:"message"` - // signature is ignored, since we verify blobs against EL versioned-hashes + Message BeaconBlockHeader `json:"message"` + Signature hexutil.Bytes `json:"signature"` } type BeaconBlockHeader struct { diff --git a/op-service/eth/blobs_api_test.go b/op-service/eth/blobs_api_test.go index 7c585cb7ac997..babad4a505375 100644 --- a/op-service/eth/blobs_api_test.go +++ b/op-service/eth/blobs_api_test.go @@ -82,17 +82,19 @@ func TestAPIGetBlobSidecarsResponse(t *testing.T) { var resp eth.APIGetBlobSidecarsResponse require.NoError(json.Unmarshal(jsonStr, &resp)) require.NotEmpty(resp.Data) - require.Equal(5, reflect.TypeOf(*resp.Data[0]).NumField(), "APIBlobSidecar changed, adjust test") - require.Equal(1, reflect.TypeOf(resp.Data[0].SignedBlockHeader).NumField(), "SignedBeaconBlockHeader changed, adjust test") + require.Equal(6, reflect.TypeOf(*resp.Data[0]).NumField(), "APIBlobSidecar changed, adjust test") + require.Equal(2, reflect.TypeOf(resp.Data[0].SignedBlockHeader).NumField(), "SignedBeaconBlockHeader changed, adjust test") require.Equal(5, reflect.TypeOf(resp.Data[0].SignedBlockHeader.Message).NumField(), "BeaconBlockHeader changed, adjust test") require.NotZero(resp.Data[0].Blob) require.NotZero(resp.Data[1].Index) require.NotZero(resp.Data[0].KZGCommitment) require.NotZero(resp.Data[0].KZGProof) + require.NotZero(resp.Data[0].InclusionProof) require.NotZero(resp.Data[0].SignedBlockHeader.Message.Slot) require.NotZero(resp.Data[0].SignedBlockHeader.Message.ParentRoot) require.NotZero(resp.Data[0].SignedBlockHeader.Message.BodyRoot) require.NotZero(resp.Data[0].SignedBlockHeader.Message.ProposerIndex) require.NotZero(resp.Data[0].SignedBlockHeader.Message.StateRoot) + require.NotZero(resp.Data[0].SignedBlockHeader.Signature) } diff --git a/op-service/eth/execution_witness.go b/op-service/eth/execution_witness.go new file mode 100644 index 0000000000000..91f274b23d7e2 --- /dev/null +++ b/op-service/eth/execution_witness.go @@ -0,0 +1,9 @@ +package eth + +import "github.com/ethereum/go-ethereum/common/hexutil" + +type ExecutionWitness struct { + Keys map[string]hexutil.Bytes `json:"keys"` + Codes map[string]hexutil.Bytes `json:"codes"` + State map[string]hexutil.Bytes `json:"state"` +} diff --git a/op-service/eth/id.go b/op-service/eth/id.go index c323d1e69b9a4..3eecdbc3bac1a 100644 --- a/op-service/eth/id.go +++ b/op-service/eth/id.go @@ -49,6 +49,15 @@ func (id L2BlockRef) TerminalString() string { return fmt.Sprintf("%s:%d", id.Hash.TerminalString(), id.Number) } +func (id L2BlockRef) BlockRef() BlockRef { + return BlockRef{ + Hash: id.Hash, + Number: id.Number, + ParentHash: id.ParentHash, + Time: id.Time, + } +} + type L1BlockRef struct { Hash common.Hash `json:"hash"` Number uint64 `json:"number"` diff --git a/op-service/eth/sync_status.go b/op-service/eth/sync_status.go index f9db1f672b824..e16275920e2bf 100644 --- a/op-service/eth/sync_status.go +++ b/op-service/eth/sync_status.go @@ -5,7 +5,7 @@ package eth type SyncStatus struct { // CurrentL1 is the L1 block that the derivation process is last idled at. // This may not be fully derived into L2 data yet. - // The safe L2 blocks were produced/included fully from the L1 chain up to and including this L1 block. + // The safe L2 blocks were produced/included fully from the L1 chain up to _but excluding_ this L1 block. // If the node is synced, this matches the HeadL1, minus the verifier confirmation distance. CurrentL1 L1BlockRef `json:"current_l1"` // CurrentL1Finalized is a legacy sync-status attribute. This is deprecated. diff --git a/op-service/eth/types.go b/op-service/eth/types.go index be5899455c213..79f2da03a0d2b 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -3,6 +3,7 @@ package eth import ( "bytes" "encoding/binary" + "encoding/json" "errors" "fmt" "math" @@ -24,9 +25,14 @@ func (c ErrorCode) IsEngineError() bool { return -38100 < c && c <= -38000 } +func (c ErrorCode) IsGenericRPCError() bool { + return -32700 < c && c <= -32600 +} + // Engine error codes used to be -3200x, but were rebased to -3800x: // https://github.com/ethereum/execution-apis/pull/214 const ( + MethodNotFound ErrorCode = -32601 // RPC method not found or not available. InvalidParams ErrorCode = -32602 UnknownPayload ErrorCode = -38001 // Payload does not exist / is not available. InvalidForkchoiceState ErrorCode = -38002 // Forkchoice state is invalid / inconsistent. @@ -37,8 +43,7 @@ const ( var ErrBedrockScalarPaddingNotEmpty = errors.New("version 0 scalar value has non-empty padding") -// InputError distinguishes an user-input error from regular rpc errors, -// to help the (Engine) API user divert from accidental input mistakes. +// InputError can be used to create rpc.Error instances with a specific error code. type InputError struct { Inner error Code ErrorCode @@ -48,6 +53,11 @@ func (ie InputError) Error() string { return fmt.Sprintf("input error %d: %s", ie.Code, ie.Inner.Error()) } +// Makes InputError implement the rpc.Error interface +func (ie InputError) ErrorCode() int { + return int(ie.Code) +} + func (ie InputError) Unwrap() error { return ie.Inner } @@ -83,6 +93,30 @@ func (b Bytes32) TerminalString() string { return fmt.Sprintf("%x..%x", b[:3], b[29:]) } +type Bytes8 [8]byte + +func (b *Bytes8) UnmarshalJSON(text []byte) error { + return hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), text, b[:]) +} + +func (b *Bytes8) UnmarshalText(text []byte) error { + return hexutil.UnmarshalFixedText("Bytes8", text, b[:]) +} + +func (b Bytes8) MarshalText() ([]byte, error) { + return hexutil.Bytes(b[:]).MarshalText() +} + +func (b Bytes8) String() string { + return hexutil.Encode(b[:]) +} + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (b Bytes8) TerminalString() string { + return fmt.Sprintf("%x", b[:]) +} + type Bytes96 [96]byte func (b *Bytes96) UnmarshalJSON(text []byte) error { @@ -170,8 +204,9 @@ type ( ) type ExecutionPayloadEnvelope struct { - ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"` - ExecutionPayload *ExecutionPayload `json:"executionPayload"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"` + ExecutionPayload *ExecutionPayload `json:"executionPayload"` + BlobsBundle *engine.BlobsBundleV1 `json:"blobsBundle"` } type ExecutionPayload struct { @@ -247,6 +282,8 @@ func (envelope *ExecutionPayloadEnvelope) CheckBlockHash() (actual common.Hash, Nonce: types.BlockNonce{}, // zeroed, proof-of-work legacy BaseFee: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(), ParentBeaconRoot: envelope.ParentBeaconBlockRoot, + BlobGasUsed: (*uint64)(payload.BlobGasUsed), + ExcessBlobGas: (*uint64)(payload.ExcessBlobGas), } if payload.CanyonBlock() { @@ -329,6 +366,33 @@ type PayloadAttributes struct { NoTxPool bool `json:"noTxPool,omitempty"` // GasLimit override GasLimit *Uint64Quantity `json:"gasLimit,omitempty"` + // EIP-1559 parameters, to be specified only post-Holocene + EIP1559Params *Bytes8 `json:"eip1559Params,omitempty"` +} + +// IsDepositsOnly returns whether all transactions of the PayloadAttributes are of Deposit +// type. Empty transactions are also considered non-Deposit transactions. +func (a *PayloadAttributes) IsDepositsOnly() bool { + for _, tx := range a.Transactions { + if len(tx) == 0 || tx[0] != types.DepositTxType { + return false + } + } + return true +} + +// WithDepositsOnly return a shallow clone with all non-Deposit transactions stripped from its +// transactions. The order is preserved. +func (a *PayloadAttributes) WithDepositsOnly() *PayloadAttributes { + clone := *a + depositTxs := make([]Data, 0, len(a.Transactions)) + for _, tx := range a.Transactions { + if len(tx) > 0 && tx[0] == types.DepositTxType { + depositTxs = append(depositTxs, tx) + } + } + clone.Transactions = depositTxs + return &clone } type ExecutePayloadStatus string @@ -390,7 +454,45 @@ type SystemConfig struct { Scalar Bytes32 `json:"scalar"` // GasLimit identifies the L2 block gas limit GasLimit uint64 `json:"gasLimit"` + // EIP1559Params contains the Holocene-encoded EIP-1559 parameters. This + // value will be 0 if Holocene is not active, or if derivation has yet to + // process any EIP_1559_PARAMS system config update events. + EIP1559Params Bytes8 `json:"eip1559Params"` // More fields can be added for future SystemConfig versions. + + // MarshalPreHolocene indicates whether or not this struct should be + // marshaled in the pre-Holocene format. The pre-Holocene format does + // not marshal the EIP1559Params field. The presence of this field in + // pre-Holocene codebases causes the rollup config to be rejected. + MarshalPreHolocene bool `json:"-"` +} + +func (sysCfg SystemConfig) MarshalJSON() ([]byte, error) { + if sysCfg.MarshalPreHolocene { + return jsonMarshalPreHolocene(sysCfg) + } + return jsonMarshalHolocene(sysCfg) +} + +func jsonMarshalHolocene(sysCfg SystemConfig) ([]byte, error) { + type sysCfgMarshaling SystemConfig + return json.Marshal(sysCfgMarshaling(sysCfg)) +} + +func jsonMarshalPreHolocene(sysCfg SystemConfig) ([]byte, error) { + type sysCfgMarshaling struct { + BatcherAddr common.Address `json:"batcherAddr"` + Overhead Bytes32 `json:"overhead"` + Scalar Bytes32 `json:"scalar"` + GasLimit uint64 `json:"gasLimit"` + } + sc := sysCfgMarshaling{ + BatcherAddr: sysCfg.BatcherAddr, + Overhead: sysCfg.Overhead, + Scalar: sysCfg.Scalar, + GasLimit: sysCfg.GasLimit, + } + return json.Marshal(sc) } // The Ecotone upgrade introduces a versioned L1 scalar format @@ -473,7 +575,7 @@ func (b *Bytes48) UnmarshalJSON(text []byte) error { } func (b *Bytes48) UnmarshalText(text []byte) error { - return hexutil.UnmarshalFixedText("Bytes32", text, b[:]) + return hexutil.UnmarshalFixedText("Bytes48", text, b[:]) } func (b Bytes48) MarshalText() ([]byte, error) { diff --git a/op-service/eth/types_test.go b/op-service/eth/types_test.go index f62c14691a163..03409e16008d5 100644 --- a/op-service/eth/types_test.go +++ b/op-service/eth/types_test.go @@ -1,10 +1,14 @@ package eth import ( + "encoding/json" "errors" "math" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" ) @@ -18,6 +22,10 @@ func TestInputError(t *testing.T) { t.Fatalf("need InputError to be detected as such") } require.ErrorIs(t, err, InputError{}, "need to detect input error with errors.Is") + + var rpcErr rpc.Error + require.ErrorAs(t, err, &rpcErr, "need input error to be rpc.Error with errors.As") + require.EqualValues(t, err.Code, rpcErr.ErrorCode()) } type scalarTest struct { @@ -66,3 +74,21 @@ func FuzzEncodeScalar(f *testing.F) { require.Equal(t, baseFeeScalar, scalars.BaseFeeScalar) }) } + +func TestSystemConfigMarshaling(t *testing.T) { + sysConfig := SystemConfig{ + BatcherAddr: common.Address{'A'}, + Overhead: Bytes32{0x4, 0x5, 0x6}, + Scalar: Bytes32{0x7, 0x8, 0x9}, + GasLimit: 1234, + // Leave EIP1559 params empty to prove that the + // zero value is sent. + } + j, err := json.Marshal(sysConfig) + require.NoError(t, err) + require.Equal(t, `{"batcherAddr":"0x4100000000000000000000000000000000000000","overhead":"0x0405060000000000000000000000000000000000000000000000000000000000","scalar":"0x0708090000000000000000000000000000000000000000000000000000000000","gasLimit":1234,"eip1559Params":"0x0000000000000000"}`, string(j)) + sysConfig.MarshalPreHolocene = true + j, err = json.Marshal(sysConfig) + require.NoError(t, err) + require.Equal(t, `{"batcherAddr":"0x4100000000000000000000000000000000000000","overhead":"0x0405060000000000000000000000000000000000000000000000000000000000","scalar":"0x0708090000000000000000000000000000000000000000000000000000000000","gasLimit":1234}`, string(j)) +} diff --git a/op-service/ioutil/streams.go b/op-service/ioutil/streams.go index 91f122906db0d..c35aefa202ef9 100644 --- a/op-service/ioutil/streams.go +++ b/op-service/ioutil/streams.go @@ -1,8 +1,10 @@ package ioutil import ( + "fmt" "io" "os" + "path/filepath" ) var ( @@ -21,6 +23,20 @@ func NoOutputStream() OutputTarget { } } +func ToBasicFile(path string, perm os.FileMode) OutputTarget { + return func() (io.Writer, io.Closer, Aborter, error) { + outDir := filepath.Dir(path) + if err := os.MkdirAll(outDir, perm); err != nil { + return nil, nil, nil, fmt.Errorf("failed to create dir %q: %w", outDir, err) + } + f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perm) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to open %q: %w", path, err) + } + return f, f, func() {}, nil + } +} + func ToAtomicFile(path string, perm os.FileMode) OutputTarget { return func() (io.Writer, io.Closer, Aborter, error) { f, err := NewAtomicWriterCompressed(path, perm) diff --git a/op-service/jsonutil/json.go b/op-service/jsonutil/json.go index 5993138595b40..8549c170d42a5 100644 --- a/op-service/jsonutil/json.go +++ b/op-service/jsonutil/json.go @@ -67,6 +67,7 @@ type jsonEncoder struct { func newJSONEncoder(w io.Writer) Encoder { e := json.NewEncoder(w) e.SetIndent("", " ") + e.SetEscapeHTML(false) return &jsonEncoder{ e: e, } diff --git a/op-service/jsonutil/merge.go b/op-service/jsonutil/merge.go new file mode 100644 index 0000000000000..8819b2ad1993c --- /dev/null +++ b/op-service/jsonutil/merge.go @@ -0,0 +1,37 @@ +package jsonutil + +import "encoding/json" + +// MergeJSON merges the provided overrides into the input struct. Fields +// must be JSON-serializable for this to work. Overrides are applied in +// order of precedence - i.e., the last overrides will override keys from +// all preceding overrides. +func MergeJSON[T any](in T, overrides ...map[string]any) (T, error) { + var out T + inJSON, err := json.Marshal(in) + if err != nil { + return out, err + } + + var tmpMap map[string]interface{} + if err := json.Unmarshal(inJSON, &tmpMap); err != nil { + return out, err + } + + for _, override := range overrides { + for k, v := range override { + tmpMap[k] = v + } + } + + inJSON, err = json.Marshal(tmpMap) + if err != nil { + return out, err + } + + if err := json.Unmarshal(inJSON, &out); err != nil { + return out, err + } + + return out, nil +} diff --git a/op-chain-ops/deployer/state/deploy_config_test.go b/op-service/jsonutil/merge_test.go similarity index 91% rename from op-chain-ops/deployer/state/deploy_config_test.go rename to op-service/jsonutil/merge_test.go index be431b5740cea..da0a400c651cc 100644 --- a/op-chain-ops/deployer/state/deploy_config_test.go +++ b/op-service/jsonutil/merge_test.go @@ -1,4 +1,4 @@ -package state +package jsonutil import ( "testing" @@ -13,7 +13,7 @@ func TestMergeJSON(t *testing.T) { C bool `json:"c"` } - out, err := mergeJSON( + out, err := MergeJSON( testStruct{ "hello", 42, diff --git a/op-service/justfile b/op-service/justfile new file mode 100644 index 0000000000000..bec1214f30871 --- /dev/null +++ b/op-service/justfile @@ -0,0 +1,23 @@ +import '../just/go.just' + +# Run tests +test: (go_test "./...") + +# Generate mocks +generate-mocks: (go_generate "./...") + +[private] +service_fuzz_task FUZZ TIME='10s': (go_fuzz FUZZ TIME "./eth") + +# Run fuzzing tests +fuzz: + printf "%s\n" \ + "FuzzExecutionPayloadUnmarshal" \ + "FuzzExecutionPayloadMarshalUnmarshalV1" \ + "FuzzExecutionPayloadMarshalUnmarshalV2" \ + "FuzzExecutionPayloadMarshalUnmarshalV3" \ + "FuzzOBP01" \ + "FuzzEncodeDecodeBlob" \ + "FuzzDetectNonBijectivity" \ + "FuzzEncodeScalar" \ + | parallel -j {{PARALLEL_JOBS}} {{just_executable()}} service_fuzz_task {} diff --git a/op-service/locks/rwmap.go b/op-service/locks/rwmap.go new file mode 100644 index 0000000000000..2a6badf25525a --- /dev/null +++ b/op-service/locks/rwmap.go @@ -0,0 +1,55 @@ +package locks + +import "sync" + +// RWMap is a simple wrapper around a map, with global Read-Write protection. +// For many concurrent reads/writes a sync.Map may be more performant, +// although it does not utilize Go generics. +// The RWMap does not have to be initialized, +// it is immediately ready for reads/writes. +type RWMap[K comparable, V any] struct { + inner map[K]V + mu sync.RWMutex +} + +func (m *RWMap[K, V]) Has(key K) (ok bool) { + m.mu.RLock() + defer m.mu.RUnlock() + _, ok = m.inner[key] + return +} + +func (m *RWMap[K, V]) Get(key K) (value V, ok bool) { + m.mu.RLock() + defer m.mu.RUnlock() + value, ok = m.inner[key] + return +} + +func (m *RWMap[K, V]) Set(key K, value V) { + m.mu.Lock() + defer m.mu.Unlock() + if m.inner == nil { + m.inner = make(map[K]V) + } + m.inner[key] = value +} + +// Range calls f sequentially for each key and value present in the map. +// If f returns false, range stops the iteration. +func (m *RWMap[K, V]) Range(f func(key K, value V) bool) { + m.mu.RLock() + defer m.mu.RUnlock() + for k, v := range m.inner { + if !f(k, v) { + break + } + } +} + +// Clear removes all key-value pairs from the map. +func (m *RWMap[K, V]) Clear() { + m.mu.Lock() + defer m.mu.Unlock() + clear(m.inner) +} diff --git a/op-service/locks/rwmap_test.go b/op-service/locks/rwmap_test.go new file mode 100644 index 0000000000000..c78fab97034b8 --- /dev/null +++ b/op-service/locks/rwmap_test.go @@ -0,0 +1,52 @@ +package locks + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRWMap(t *testing.T) { + m := &RWMap[uint64, int64]{} + + // get on new map + v, ok := m.Get(123) + require.False(t, ok) + require.Equal(t, int64(0), v) + + // set a value + m.Set(123, 42) + v, ok = m.Get(123) + require.True(t, ok) + require.Equal(t, int64(42), v) + + // overwrite a value + m.Set(123, -42) + v, ok = m.Get(123) + require.True(t, ok) + require.Equal(t, int64(-42), v) + + // add a value + m.Set(10, 100) + + // range over values + got := make(map[uint64]int64) + m.Range(func(key uint64, value int64) bool { + if _, ok := got[key]; ok { + panic("duplicate") + } + got[key] = value + return true + }) + require.Len(t, got, 2) + require.Equal(t, int64(100), got[uint64(10)]) + require.Equal(t, int64(-42), got[uint64(123)]) + + // range and stop early + clear(got) + m.Range(func(key uint64, value int64) bool { + got[key] = value + return false + }) + require.Len(t, got, 1, "stop early") +} diff --git a/op-service/locks/rwvalue.go b/op-service/locks/rwvalue.go new file mode 100644 index 0000000000000..12ca65e61d738 --- /dev/null +++ b/op-service/locks/rwvalue.go @@ -0,0 +1,24 @@ +package locks + +import "sync" + +// RWValue is a simple container struct, to deconflict reads/writes of the value, +// without locking up a bigger structure in the caller. +// It exposes the underlying RWLock and Value for direct access where needed. +type RWValue[E any] struct { + sync.RWMutex + Value E +} + +func (c *RWValue[E]) Get() (out E) { + c.RLock() + defer c.RUnlock() + out = c.Value + return +} + +func (c *RWValue[E]) Set(v E) { + c.Lock() + defer c.Unlock() + c.Value = v +} diff --git a/op-service/locks/rwvalue_test.go b/op-service/locks/rwvalue_test.go new file mode 100644 index 0000000000000..f99d9345a1cc4 --- /dev/null +++ b/op-service/locks/rwvalue_test.go @@ -0,0 +1,16 @@ +package locks + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRWValue(t *testing.T) { + v := &RWValue[uint64]{} + require.Equal(t, uint64(0), v.Get()) + v.Set(123) + require.Equal(t, uint64(123), v.Get()) + v.Set(42) + require.Equal(t, uint64(42), v.Get()) +} diff --git a/op-service/metrics/factory.go b/op-service/metrics/factory.go index 0bd4da2373dbb..315b7c52e0580 100644 --- a/op-service/metrics/factory.go +++ b/op-service/metrics/factory.go @@ -9,6 +9,7 @@ type Factory interface { NewCounter(opts prometheus.CounterOpts) prometheus.Counter NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *prometheus.CounterVec NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge + NewGaugeFunc(opts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec NewHistogram(opts prometheus.HistogramOpts) prometheus.Histogram NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec @@ -63,6 +64,15 @@ func (d *documentor) NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge { return d.factory.NewGauge(opts) } +func (d *documentor) NewGaugeFunc(opts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc { + d.metrics = append(d.metrics, DocumentedMetric{ + Type: "gauge", + Name: fullName(opts.Namespace, opts.Subsystem, opts.Name), + Help: opts.Help, + }) + return d.factory.NewGaugeFunc(opts, function) +} + func (d *documentor) NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec { d.metrics = append(d.metrics, DocumentedMetric{ Type: "gauge", diff --git a/op-service/predeploys/addresses.go b/op-service/predeploys/addresses.go index 0b69df3bb8343..4bedd342e531b 100644 --- a/op-service/predeploys/addresses.go +++ b/op-service/predeploys/addresses.go @@ -25,10 +25,12 @@ const ( L1FeeVault = "0x420000000000000000000000000000000000001a" SchemaRegistry = "0x4200000000000000000000000000000000000020" EAS = "0x4200000000000000000000000000000000000021" + SoulGasToken = "0x4200000000000000000000000000000000000800" CrossL2Inbox = "0x4200000000000000000000000000000000000022" L2toL2CrossDomainMessenger = "0x4200000000000000000000000000000000000023" SuperchainWETH = "0x4200000000000000000000000000000000000024" ETHLiquidity = "0x4200000000000000000000000000000000000025" + SuperchainTokenBridge = "0x4200000000000000000000000000000000000028" Create2Deployer = "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2" MultiCall3 = "0xcA11bde05977b3631167028862bE2a173976CA11" Safe_v130 = "0x69f4D1788e39c87893C980c06EdF4b7f686e2938" @@ -64,10 +66,12 @@ var ( L1FeeVaultAddr = common.HexToAddress(L1FeeVault) SchemaRegistryAddr = common.HexToAddress(SchemaRegistry) EASAddr = common.HexToAddress(EAS) + SoulGasTokenAddr = common.HexToAddress(SoulGasToken) CrossL2InboxAddr = common.HexToAddress(CrossL2Inbox) L2toL2CrossDomainMessengerAddr = common.HexToAddress(L2toL2CrossDomainMessenger) SuperchainWETHAddr = common.HexToAddress(SuperchainWETH) ETHLiquidityAddr = common.HexToAddress(ETHLiquidity) + SuperchainTokenBridgeAddr = common.HexToAddress(SuperchainTokenBridge) Create2DeployerAddr = common.HexToAddress(Create2Deployer) MultiCall3Addr = common.HexToAddress(MultiCall3) Safe_v130Addr = common.HexToAddress(Safe_v130) @@ -101,6 +105,7 @@ func init() { Predeploys["L2toL2CrossDomainMessenger"] = &Predeploy{Address: L2toL2CrossDomainMessengerAddr} Predeploys["SuperchainWETH"] = &Predeploy{Address: SuperchainWETHAddr} Predeploys["ETHLiquidity"] = &Predeploy{Address: ETHLiquidityAddr} + Predeploys["SuperchainTokenBridge"] = &Predeploy{Address: SuperchainTokenBridgeAddr} Predeploys["GovernanceToken"] = &Predeploy{ Address: GovernanceTokenAddr, ProxyDisabled: true, @@ -116,6 +121,7 @@ func init() { Predeploys["L1FeeVault"] = &Predeploy{Address: L1FeeVaultAddr} Predeploys["SchemaRegistry"] = &Predeploy{Address: SchemaRegistryAddr} Predeploys["EAS"] = &Predeploy{Address: EASAddr} + Predeploys["SoulGasToken"] = &Predeploy{Address: SoulGasTokenAddr} Predeploys["Create2Deployer"] = &Predeploy{ Address: Create2DeployerAddr, ProxyDisabled: true, diff --git a/op-service/signer/blockpayload_args.go b/op-service/signer/blockpayload_args.go new file mode 100644 index 0000000000000..8239bc0967d6b --- /dev/null +++ b/op-service/signer/blockpayload_args.go @@ -0,0 +1,62 @@ +package signer + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// BlockPayloadArgs represents the arguments to sign a new block payload from the sequencer. +type BlockPayloadArgs struct { + Domain [32]byte `json:"domain"` + ChainID *big.Int `json:"chainId"` + PayloadHash []byte `json:"payloadHash"` + PayloadBytes []byte + SenderAddress *common.Address `json:"senderAddress"` +} + +// NewBlockPayloadArgs creates a BlockPayloadArgs struct +func NewBlockPayloadArgs(domain [32]byte, chainId *big.Int, payloadBytes []byte, senderAddress *common.Address) *BlockPayloadArgs { + payloadHash := crypto.Keccak256(payloadBytes) + args := &BlockPayloadArgs{ + Domain: domain, + ChainID: chainId, + PayloadHash: payloadHash, + PayloadBytes: payloadBytes, + SenderAddress: senderAddress, + } + return args +} + +func (args *BlockPayloadArgs) Check() error { + if args.ChainID == nil { + return errors.New("chainId not specified") + } + if len(args.PayloadHash) == 0 { + return errors.New("payloadHash not specified") + } + return nil +} + +// ToSigningHash creates a signingHash from the block payload args. +// Uses the hashing scheme from https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/rollup-node-p2p.md#block-signatures +func (args *BlockPayloadArgs) ToSigningHash() (common.Hash, error) { + if err := args.Check(); err != nil { + return common.Hash{}, err + } + var msgInput [32 + 32 + 32]byte + // domain: first 32 bytes + copy(msgInput[:32], args.Domain[:]) + // chain_id: second 32 bytes + if args.ChainID.BitLen() > 256 { + return common.Hash{}, errors.New("chain_id is too large") + } + args.ChainID.FillBytes(msgInput[32:64]) + + // payload_hash: third 32 bytes, hash of encoded payload + copy(msgInput[64:], args.PayloadHash[:]) + + return crypto.Keccak256Hash(msgInput[:]), nil +} diff --git a/op-service/signer/cli.go b/op-service/signer/cli.go index 1da5330dc4d61..534fd09c97bca 100644 --- a/op-service/signer/cli.go +++ b/op-service/signer/cli.go @@ -2,6 +2,8 @@ package signer import ( "errors" + "net/http" + "strings" "github.com/urfave/cli/v2" @@ -12,34 +14,44 @@ import ( const ( EndpointFlagName = "signer.endpoint" AddressFlagName = "signer.address" + HeadersFlagName = "signer.header" ) -func CLIFlags(envPrefix string) []cli.Flag { +func CLIFlags(envPrefix string, category string) []cli.Flag { envPrefix += "_SIGNER" flags := []cli.Flag{ &cli.StringFlag{ - Name: EndpointFlagName, - Usage: "Signer endpoint the client will connect to", - EnvVars: opservice.PrefixEnvVar(envPrefix, "ENDPOINT"), + Name: EndpointFlagName, + Usage: "Signer endpoint the client will connect to", + EnvVars: opservice.PrefixEnvVar(envPrefix, "ENDPOINT"), + Category: category, }, &cli.StringFlag{ - Name: AddressFlagName, - Usage: "Address the signer is signing transactions for", - EnvVars: opservice.PrefixEnvVar(envPrefix, "ADDRESS"), + Name: AddressFlagName, + Usage: "Address the signer is signing requests for", + EnvVars: opservice.PrefixEnvVar(envPrefix, "ADDRESS"), + Category: category, + }, + &cli.StringSliceFlag{ + Name: HeadersFlagName, + Usage: "Headers to pass to the remote signer. Format `key=value`. Value can contain any character allowed in a HTTP header. When using env vars, split with commas. When using flags one key value pair per flag.", + EnvVars: opservice.PrefixEnvVar(envPrefix, "HEADER"), }, } - flags = append(flags, optls.CLIFlagsWithFlagPrefix(envPrefix, "signer")...) + flags = append(flags, optls.CLIFlagsWithFlagPrefix(envPrefix, "signer", category)...) return flags } type CLIConfig struct { Endpoint string Address string + Headers http.Header TLSConfig optls.CLIConfig } func NewCLIConfig() CLIConfig { return CLIConfig{ + Headers: http.Header{}, TLSConfig: optls.NewCLIConfig(), } } @@ -55,16 +67,24 @@ func (c CLIConfig) Check() error { } func (c CLIConfig) Enabled() bool { - if c.Endpoint != "" && c.Address != "" { - return true - } - return false + return c.Endpoint != "" && c.Address != "" } func ReadCLIConfig(ctx *cli.Context) CLIConfig { + var headers = http.Header{} + if ctx.StringSlice(HeadersFlagName) != nil { + for _, header := range ctx.StringSlice(HeadersFlagName) { + args := strings.SplitN(header, "=", 2) + if len(args) == 2 { + headers.Set(args[0], args[1]) + } + } + } + cfg := CLIConfig{ Endpoint: ctx.String(EndpointFlagName), Address: ctx.String(AddressFlagName), + Headers: headers, TLSConfig: optls.ReadCLIConfigWithPrefix(ctx, "signer"), } return cfg diff --git a/op-service/signer/cli_test.go b/op-service/signer/cli_test.go index a7fd7385a30e8..3453258c6ca1c 100644 --- a/op-service/signer/cli_test.go +++ b/op-service/signer/cli_test.go @@ -1,6 +1,7 @@ package signer import ( + "net/http" "testing" "github.com/stretchr/testify/require" @@ -18,6 +19,37 @@ func TestDefaultConfigIsValid(t *testing.T) { require.NoError(t, err) } +func TestHeaderParsing(t *testing.T) { + testHeaders := []string{ + "test-key=this:is:a:value", + "b64-test-key=value:dGVzdCBkYXRhIDE=$", + } + + args := []string{"app", "--signer.header", testHeaders[0], "--signer.header", testHeaders[1]} + cfg := configForArgs(args...) + + expectedHeaders := http.Header{} + expectedHeaders.Set("test-key", "this:is:a:value") + expectedHeaders.Set("b64-test-key", "value:dGVzdCBkYXRhIDE=$") + + require.Equal(t, expectedHeaders, cfg.Headers) +} + +func TestHeaderParsingWithComma(t *testing.T) { + testHeaders := []string{ + "test-key=this:is:a:value,b64-test-key=value:dGVzdCBkYXRhIDE=$", + } + + args := []string{"app", "--signer.header", testHeaders[0]} + cfg := configForArgs(args...) + + expectedHeaders := http.Header{} + expectedHeaders.Set("test-key", "this:is:a:value") + expectedHeaders.Set("b64-test-key", "value:dGVzdCBkYXRhIDE=$") + + require.Equal(t, expectedHeaders, cfg.Headers) +} + func TestInvalidConfig(t *testing.T) { tests := []struct { name string @@ -29,6 +61,7 @@ func TestInvalidConfig(t *testing.T) { expected: "signer endpoint and address must both be set or not set", configChange: func(config *CLIConfig) { config.Address = "0x1234" + config.TLSConfig.Enabled = true }, }, { @@ -36,6 +69,7 @@ func TestInvalidConfig(t *testing.T) { expected: "signer endpoint and address must both be set or not set", configChange: func(config *CLIConfig) { config.Endpoint = "http://localhost" + config.TLSConfig.Enabled = true }, }, { @@ -43,6 +77,7 @@ func TestInvalidConfig(t *testing.T) { expected: "all tls flags must be set if at least one is set", configChange: func(config *CLIConfig) { config.TLSConfig.TLSKey = "" + config.TLSConfig.Enabled = true }, }, } @@ -58,7 +93,7 @@ func TestInvalidConfig(t *testing.T) { func configForArgs(args ...string) CLIConfig { app := cli.NewApp() - app.Flags = CLIFlags("TEST_") + app.Flags = CLIFlags("TEST_", "") app.Name = "test" var config CLIConfig app.Action = func(ctx *cli.Context) error { diff --git a/op-service/signer/client.go b/op-service/signer/client.go index 9822a1a7409f1..acd753ecef7ac 100644 --- a/op-service/signer/client.go +++ b/op-service/signer/client.go @@ -25,9 +25,9 @@ type SignerClient struct { logger log.Logger } -func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConfig) (*SignerClient, error) { +func NewSignerClient(logger log.Logger, endpoint string, headers http.Header, tlsConfig optls.CLIConfig) (*SignerClient, error) { var httpClient *http.Client - if tlsConfig.TLSCaCert != "" { + if tlsConfig.Enabled { logger.Info("tlsConfig specified, loading tls config") caCert, err := os.ReadFile(tlsConfig.TLSCaCert) if err != nil { @@ -63,7 +63,7 @@ func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConf httpClient = http.DefaultClient } - rpcClient, err := rpc.DialOptions(context.Background(), endpoint, rpc.WithHTTPClient(httpClient)) + rpcClient, err := rpc.DialOptions(context.Background(), endpoint, rpc.WithHTTPClient(httpClient), rpc.WithHeaders(headers)) if err != nil { return nil, err } @@ -79,7 +79,7 @@ func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConf } func NewSignerClientFromConfig(logger log.Logger, config CLIConfig) (*SignerClient, error) { - return NewSignerClient(logger, config.Endpoint, config.TLSConfig) + return NewSignerClient(logger, config.Endpoint, config.Headers, config.TLSConfig) } func (s *SignerClient) pingVersion() (string, error) { @@ -113,3 +113,19 @@ func (s *SignerClient) SignTransaction(ctx context.Context, chainId *big.Int, fr return &signed, nil } + +func (s *SignerClient) SignBlockPayload(ctx context.Context, args *BlockPayloadArgs) ([65]byte, error) { + var result hexutil.Bytes + + if err := s.client.CallContext(ctx, &result, "opsigner_signBlockPayload", args); err != nil { + return [65]byte{}, fmt.Errorf("opsigner_signBlockPayload failed: %w", err) + } + + if len(result) != 65 { + return [65]byte{}, fmt.Errorf("invalid signature: %s", result.String()) + } + + signature := [65]byte(result) + + return signature, nil +} diff --git a/op-service/sources/engine_client.go b/op-service/sources/engine_client.go index 79921192f9e75..8e06336b18035 100644 --- a/op-service/sources/engine_client.go +++ b/op-service/sources/engine_client.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/client" @@ -86,11 +85,7 @@ func (s *EngineAPIClient) EngineVersionProvider() EngineVersionProvider { return // ForkchoiceUpdate updates the forkchoice on the execution client. If attributes is not nil, the engine client will also begin building a block // based on attributes after the new head block and return the payload ID. -// -// The RPC may return three types of errors: -// 1. Processing error: ForkchoiceUpdatedResult.PayloadStatusV1.ValidationError or other non-success PayloadStatusV1, -// 2. `error` as eth.InputError: the forkchoice state or attributes are not valid. -// 3. Other types of `error`: temporary RPC errors, like timeouts. +// It's the caller's responsibility to check the error type, and in case of an rpc.Error, check the ErrorCode. func (s *EngineAPIClient) ForkchoiceUpdate(ctx context.Context, fc *eth.ForkchoiceState, attributes *eth.PayloadAttributes) (*eth.ForkchoiceUpdatedResult, error) { llog := s.log.New("state", fc) // local logger tlog := llog.New("attr", attributes) // trace logger @@ -100,28 +95,15 @@ func (s *EngineAPIClient) ForkchoiceUpdate(ctx context.Context, fc *eth.Forkchoi var result eth.ForkchoiceUpdatedResult method := s.evp.ForkchoiceUpdatedVersion(attributes) err := s.RPC.CallContext(fcCtx, &result, string(method), fc, attributes) - if err == nil { - tlog.Trace("Shared forkchoice-updated signal") - if attributes != nil { // block building is optional, we only get a payload ID if we are building a block - tlog.Trace("Received payload id", "payloadId", result.PayloadID) - } - return &result, nil - } else { + if err != nil { llog.Warn("Failed to share forkchoice-updated signal", "err", err) - if rpcErr, ok := err.(rpc.Error); ok { - code := eth.ErrorCode(rpcErr.ErrorCode()) - switch code { - case eth.InvalidParams, eth.InvalidForkchoiceState, eth.InvalidPayloadAttributes: - return nil, eth.InputError{ - Inner: err, - Code: code, - } - default: - return nil, fmt.Errorf("unrecognized rpc error: %w", err) - } - } return nil, err } + tlog.Trace("Shared forkchoice-updated signal") + if attributes != nil { // block building is optional, we only get a payload ID if we are building a block + tlog.Trace("Received payload id", "payloadId", result.PayloadID) + } + return &result, nil } // NewPayload executes a full block on the execution engine. @@ -138,6 +120,8 @@ func (s *EngineAPIClient) NewPayload(ctx context.Context, payload *eth.Execution var err error switch method := s.evp.NewPayloadVersion(uint64(payload.Timestamp)); method { case eth.NewPayloadV3: + // now we pass empty array to skip checking versionedHashes + // TODO: sync with OP upstream once they support L2 blob tx err = s.RPC.CallContext(execCtx, &result, string(method), payload, []common.Hash{}, parentBeaconBlockRoot) case eth.NewPayloadV2: err = s.RPC.CallContext(execCtx, &result, string(method), payload) @@ -154,9 +138,7 @@ func (s *EngineAPIClient) NewPayload(ctx context.Context, payload *eth.Execution } // GetPayload gets the execution payload associated with the PayloadId. -// There may be two types of error: -// 1. `error` as eth.InputError: the payload ID may be unknown -// 2. Other types of `error`: temporary RPC errors, like timeouts. +// It's the caller's responsibility to check the error type, and in case of an rpc.Error, check the ErrorCode. func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) { e := s.log.New("payload_id", payloadInfo.ID) e.Trace("getting payload") @@ -165,18 +147,6 @@ func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.Payloa err := s.RPC.CallContext(ctx, &result, string(method), payloadInfo.ID) if err != nil { e.Warn("Failed to get payload", "payload_id", payloadInfo.ID, "err", err) - if rpcErr, ok := err.(rpc.Error); ok { - code := eth.ErrorCode(rpcErr.ErrorCode()) - switch code { - case eth.UnknownPayload: - return nil, eth.InputError{ - Inner: err, - Code: code, - } - default: - return nil, fmt.Errorf("unrecognized rpc error: %w", err) - } - } return nil, err } e.Trace("Received payload") diff --git a/op-service/sources/eth_client.go b/op-service/sources/eth_client.go index 149a0c08ababd..39587fd197504 100644 --- a/op-service/sources/eth_client.go +++ b/op-service/sources/eth_client.go @@ -315,6 +315,22 @@ func (s *EthClient) FetchReceipts(ctx context.Context, blockHash common.Hash) (e return info, receipts, nil } +// ExecutionWitness fetches execution witness data for a block number. +func (s *EthClient) ExecutionWitness(ctx context.Context, blockNum uint64) (*eth.ExecutionWitness, error) { + var witness *eth.ExecutionWitness + + err := s.client.CallContext(ctx, &witness, "debug_executionWitness", hexutil.EncodeUint64(blockNum), true) + if err != nil { + return nil, err + } + + if witness == nil { + return nil, ethereum.NotFound + } + + return witness, nil +} + // GetProof returns an account proof result, with any optional requested storage proofs. // The retrieval does sanity-check that storage proofs for the expected keys are present in the response, // but does not verify the result. Call accountResult.Verify(stateRoot) to verify the result. @@ -331,8 +347,8 @@ func (s *EthClient) GetProof(ctx context.Context, address common.Address, storag return nil, fmt.Errorf("missing storage proof data, got %d proof entries but requested %d storage keys", len(getProofResponse.StorageProof), len(storage)) } for i, key := range storage { - if key != getProofResponse.StorageProof[i].Key { - return nil, fmt.Errorf("unexpected storage proof key difference for entry %d: got %s but requested %s", i, getProofResponse.StorageProof[i].Key, key) + if key != common.BigToHash(getProofResponse.StorageProof[i].Key.ToInt()) { + return nil, fmt.Errorf("unexpected storage proof key difference for entry %d: got %s but requested %s", i, getProofResponse.StorageProof[i].Key.String(), key) } } return getProofResponse, nil diff --git a/op-service/sources/receipts_basic_test.go b/op-service/sources/receipts_basic_test.go index 3a0bbdf2219d4..ad1ed16013adc 100644 --- a/op-service/sources/receipts_basic_test.go +++ b/op-service/sources/receipts_basic_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" @@ -97,38 +99,55 @@ func TestBasicRPCReceiptsFetcher_Reuse(t *testing.T) { func TestBasicRPCReceiptsFetcher_Concurrency(t *testing.T) { require := require.New(t) const numFetchers = 32 - batchSize, txCount := 4, uint64(18) // 4.5 * 4 + const batchSize, txCount = 4, 16 + const numBatchCalls = txCount / batchSize block, receipts := randomRpcBlockAndReceipts(rand.New(rand.NewSource(123)), txCount) recMap := make(map[common.Hash]*types.Receipt, len(receipts)) for _, rec := range receipts { recMap[rec.TxHash] = rec } - mrpc := new(mockRPC) - rp := NewBasicRPCReceiptsFetcher(mrpc, batchSize) - // prepare mock - var numCalls atomic.Int32 - mrpc.On("BatchCallContext", mock.Anything, mock.AnythingOfType("[]rpc.BatchElem")). - Run(func(args mock.Arguments) { - numCalls.Add(1) - els := args.Get(1).([]rpc.BatchElem) - for _, el := range els { - if el.Method == "eth_getTransactionReceipt" { - txHash := el.Args[0].(common.Hash) - // The IterativeBatchCall expects that the values are written - // to the fields of the allocated *types.Receipt. - **(el.Result.(**types.Receipt)) = *recMap[txHash] + boff := &retry.ExponentialStrategy{ + Min: 0, + Max: time.Second, + MaxJitter: 100 * time.Millisecond, + } + err := retry.Do0(context.Background(), 10, boff, func() error { + mrpc := new(mockRPC) + rp := NewBasicRPCReceiptsFetcher(mrpc, batchSize) + + // prepare mock + var numCalls atomic.Int32 + mrpc.On("BatchCallContext", mock.Anything, mock.AnythingOfType("[]rpc.BatchElem")). + Run(func(args mock.Arguments) { + numCalls.Add(1) + els := args.Get(1).([]rpc.BatchElem) + for _, el := range els { + if el.Method == "eth_getTransactionReceipt" { + txHash := el.Args[0].(common.Hash) + // The IterativeBatchCall expects that the values are written + // to the fields of the allocated *types.Receipt. + **(el.Result.(**types.Receipt)) = *recMap[txHash] + } } - } - }). - Return([]error{nil}) + }). + Return([]error{nil}) - runConcurrentFetchingTest(t, rp, numFetchers, receipts, block) + runConcurrentFetchingTest(t, rp, numFetchers, receipts, block) - mrpc.AssertExpectations(t) - finalNumCalls := int(numCalls.Load()) - require.NotZero(finalNumCalls, "BatchCallContext should have been called.") - require.Less(finalNumCalls, numFetchers, "Some IterativeBatchCalls should have been shared.") + mrpc.AssertExpectations(t) + finalNumCalls := int(numCalls.Load()) + + if finalNumCalls == 0 { + return errors.New("batchCallContext should have been called") + } + + if finalNumCalls >= numFetchers*numBatchCalls { + return errors.New("some IterativeBatchCalls should have been shared") + } + return nil + }) + require.NoError(err) } func runConcurrentFetchingTest(t *testing.T, rp ReceiptsProvider, numFetchers int, receipts types.Receipts, block *RPCBlock) { diff --git a/op-service/sources/receipts_rpc.go b/op-service/sources/receipts_rpc.go index ecf2c85822105..962c7391a76b5 100644 --- a/op-service/sources/receipts_rpc.go +++ b/op-service/sources/receipts_rpc.go @@ -329,8 +329,7 @@ func AvailableReceiptsFetchingMethods(kind RPCProviderKind) ReceiptsFetchingMeth case RPCKindQuickNode: return DebugGetRawReceipts | EthGetBlockReceipts | EthGetTransactionReceiptBatch case RPCKindInfura: - // Infura is big, but sadly does not support more optimized receipts fetching methods (yet?) - return EthGetTransactionReceiptBatch + return EthGetBlockReceipts | EthGetTransactionReceiptBatch case RPCKindParity: return ParityGetBlockReceipts | EthGetTransactionReceiptBatch case RPCKindNethermind: diff --git a/op-service/sources/receipts_test.go b/op-service/sources/receipts_test.go index 088a3d9b22cb4..230fcf0e72b54 100644 --- a/op-service/sources/receipts_test.go +++ b/op-service/sources/receipts_test.go @@ -283,7 +283,7 @@ func TestEthClient_FetchReceipts(t *testing.T) { { name: "infura", providerKind: RPCKindInfura, - setup: fallbackCase(4, EthGetTransactionReceiptBatch), + setup: fallbackCase(4, EthGetBlockReceipts, EthGetTransactionReceiptBatch), }, { name: "nethermind", diff --git a/op-service/sources/supervisor_client.go b/op-service/sources/supervisor_client.go index d9cb71fb45b2f..d6191b9cfb208 100644 --- a/op-service/sources/supervisor_client.go +++ b/op-service/sources/supervisor_client.go @@ -4,12 +4,10 @@ import ( "context" "fmt" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" ) type SupervisorClient struct { @@ -22,9 +20,7 @@ func NewSupervisorClient(client client.RPC) *SupervisorClient { } } -func (cl *SupervisorClient) Stop( - ctx context.Context, -) error { +func (cl *SupervisorClient) Stop(ctx context.Context) error { var result error err := cl.client.CallContext( ctx, @@ -36,9 +32,7 @@ func (cl *SupervisorClient) Stop( return result } -func (cl *SupervisorClient) Start( - ctx context.Context, -) error { +func (cl *SupervisorClient) Start(ctx context.Context) error { var result error err := cl.client.CallContext( ctx, @@ -50,10 +44,7 @@ func (cl *SupervisorClient) Start( return result } -func (cl *SupervisorClient) AddL2RPC( - ctx context.Context, - rpc string, -) error { +func (cl *SupervisorClient) AddL2RPC(ctx context.Context, rpc string) error { var result error err := cl.client.CallContext( ctx, @@ -66,9 +57,33 @@ func (cl *SupervisorClient) AddL2RPC( return result } +func (cl *SupervisorClient) CheckMessage(ctx context.Context, identifier types.Identifier, logHash common.Hash) (types.SafetyLevel, error) { + var result types.SafetyLevel + err := cl.client.CallContext( + ctx, + &result, + "supervisor_checkMessage", + identifier, + logHash) + if err != nil { + return types.Invalid, fmt.Errorf("failed to check message (chain %s), (block %v), (index %v), (logHash %s): %w", + identifier.ChainID, + identifier.BlockNumber, + identifier.LogIndex, + logHash, + err) + } + return result, nil +} + func (cl *SupervisorClient) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) { var result types.ReferenceView - err := cl.client.CallContext(ctx, &result, "supervisor_unsafeView", (*hexutil.U256)(&chainID), unsafe) + err := cl.client.CallContext( + ctx, + &result, + "supervisor_unsafeView", + chainID, + unsafe) if err != nil { return types.ReferenceView{}, fmt.Errorf("failed to share unsafe block view %s (chain %s): %w", unsafe, chainID, err) } @@ -77,7 +92,12 @@ func (cl *SupervisorClient) UnsafeView(ctx context.Context, chainID types.ChainI func (cl *SupervisorClient) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) { var result types.ReferenceView - err := cl.client.CallContext(ctx, &result, "supervisor_safeView", (*hexutil.U256)(&chainID), safe) + err := cl.client.CallContext( + ctx, + &result, + "supervisor_safeView", + chainID, + safe) if err != nil { return types.ReferenceView{}, fmt.Errorf("failed to share safe block view %s (chain %s): %w", safe, chainID, err) } @@ -86,26 +106,51 @@ func (cl *SupervisorClient) SafeView(ctx context.Context, chainID types.ChainID, func (cl *SupervisorClient) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) { var result eth.BlockID - err := cl.client.CallContext(ctx, &result, "supervisor_finalized", chainID) + err := cl.client.CallContext( + ctx, + &result, + "supervisor_finalized", + chainID) return result, err } -func (cl *SupervisorClient) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) { - var result eth.L1BlockRef - err := cl.client.CallContext(ctx, &result, "supervisor_derivedFrom", chainID, blockHash, blockNumber) +func (cl *SupervisorClient) CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (eth.BlockRef, error) { + var result eth.BlockRef + err := cl.client.CallContext( + ctx, + &result, + "supervisor_crossDerivedFrom", + chainID, + derived) return result, err } -func (cl *SupervisorClient) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error { - return cl.client.CallContext(ctx, nil, "supervisor_updateLocalUnsafe", chainID, head) +func (cl *SupervisorClient) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error { + return cl.client.CallContext( + ctx, + nil, + "supervisor_updateLocalUnsafe", + chainID, + head) } -func (cl *SupervisorClient) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error { - return cl.client.CallContext(ctx, nil, "supervisor_updateLocalSafe", chainID, derivedFrom, lastDerived) +func (cl *SupervisorClient) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error { + return cl.client.CallContext( + ctx, + nil, + "supervisor_updateLocalSafe", + chainID, + derivedFrom, + lastDerived) } func (cl *SupervisorClient) UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalizedL1 eth.L1BlockRef) error { - return cl.client.CallContext(ctx, nil, "supervisor_updateFinalizedL1", chainID, finalizedL1) + return cl.client.CallContext( + ctx, + nil, + "supervisor_updateFinalizedL1", + chainID, + finalizedL1) } func (cl *SupervisorClient) Close() { diff --git a/op-service/testlog/testlog.go b/op-service/testlog/testlog.go index fe7ede207cbc1..965fbe9d47b22 100644 --- a/op-service/testlog/testlog.go +++ b/op-service/testlog/testlog.go @@ -159,11 +159,22 @@ func (l *logger) flush() { scanner := bufio.NewScanner(l.buf) for scanner.Scan() { - l.t.Logf("%*s%s", padding, "", scanner.Text()) + l.internalFlush("%*s%s", padding, "", scanner.Text()) } l.buf.Reset() } +func (l *logger) internalFlush(format string, args ...any) { + defer func() { + if r := recover(); r != nil { + log.Warn("testlog: panic during flush", "recover", r) + } + }() + + l.t.Helper() + l.t.Logf(format, args...) +} + // The Go testing lib uses the runtime package to get info about the calling site, and then decorates the line. // We can't disable this decoration, but we can adjust the contents to align by padding after the info. // To pad the right amount, we estimate how long the info is. diff --git a/op-service/testutils/anvil/anvil.go b/op-service/testutils/anvil/anvil.go index c203712b32053..406d7d07c77ed 100644 --- a/op-service/testutils/anvil/anvil.go +++ b/op-service/testutils/anvil/anvil.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "testing" "github.com/ethereum/go-ethereum/log" @@ -21,8 +22,6 @@ func Test(t *testing.T) { } } -const AnvilPort = 31967 - type Runner struct { proc *exec.Cmd stdout io.ReadCloser @@ -30,14 +29,21 @@ type Runner struct { logger log.Logger startedCh chan struct{} wg sync.WaitGroup + port int32 } func New(l1RPCURL string, logger log.Logger) (*Runner, error) { + if _, err := exec.LookPath("anvil"); err != nil { + return nil, fmt.Errorf("anvil not found in PATH: %w", err) + } + proc := exec.Command( "anvil", "--fork-url", l1RPCURL, "--port", - strconv.Itoa(AnvilPort), + "0", + "--base-fee", + "1000000000", ) stdout, err := proc.StdoutPipe() if err != nil { @@ -88,21 +94,31 @@ func (r *Runner) Stop() error { func (r *Runner) outputStream(stream io.ReadCloser) { defer r.wg.Done() scanner := bufio.NewScanner(stream) - listenLine := fmt.Sprintf("Listening on 127.0.0.1:%d", AnvilPort) - started := sync.OnceFunc(func() { - r.startedCh <- struct{}{} - }) + listenLine := "Listening on 127.0.0.1" for scanner.Scan() { line := scanner.Text() - if strings.Contains(line, listenLine) { - started() + + if strings.Contains(line, listenLine) && atomic.LoadInt32(&r.port) == 0 { + split := strings.Split(line, ":") + port, err := strconv.Atoi(strings.TrimSpace(split[len(split)-1])) + if err == nil { + atomic.StoreInt32(&r.port, int32(port)) + r.startedCh <- struct{}{} + } else { + r.logger.Error("failed to parse port from Anvil output", "err", err) + } } - r.logger.Debug("[ANVIL] " + scanner.Text()) + r.logger.Debug("[ANVIL] " + line) } } func (r *Runner) RPCUrl() string { - return fmt.Sprintf("http://localhost:%d", AnvilPort) + port := atomic.LoadInt32(&r.port) + if port == 0 { + panic("anvil not started") + } + + return fmt.Sprintf("http://localhost:%d", port) } diff --git a/op-service/testutils/fake_interop_backend.go b/op-service/testutils/fake_interop_backend.go index 7c6287031229c..4c8624d535525 100644 --- a/op-service/testutils/fake_interop_backend.go +++ b/op-service/testutils/fake_interop_backend.go @@ -3,8 +3,6 @@ package testutils import ( "context" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -13,9 +11,9 @@ type FakeInteropBackend struct { UnsafeViewFn func(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) SafeViewFn func(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) FinalizedFn func(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) - DerivedFromFn func(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) - UpdateLocalUnsafeFn func(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error - UpdateLocalSafeFn func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error + DerivedFromFn func(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (eth.L1BlockRef, error) + UpdateLocalUnsafeFn func(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error + UpdateLocalSafeFn func(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error UpdateFinalizedL1Fn func(ctx context.Context, chainID types.ChainID, finalized eth.L1BlockRef) error } @@ -31,15 +29,15 @@ func (m *FakeInteropBackend) Finalized(ctx context.Context, chainID types.ChainI return m.FinalizedFn(ctx, chainID) } -func (m *FakeInteropBackend) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) { - return m.DerivedFromFn(ctx, chainID, blockHash, blockNumber) +func (m *FakeInteropBackend) CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (eth.L1BlockRef, error) { + return m.DerivedFromFn(ctx, chainID, derived) } -func (m *FakeInteropBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error { +func (m *FakeInteropBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error { return m.UpdateLocalUnsafeFn(ctx, chainID, head) } -func (m *FakeInteropBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error { +func (m *FakeInteropBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error { return m.UpdateLocalSafeFn(ctx, chainID, derivedFrom, lastDerived) } diff --git a/op-service/testutils/kurtosisutil/runner.go b/op-service/testutils/kurtosisutil/runner.go index 6440cedef681c..6a847c9623501 100644 --- a/op-service/testutils/kurtosisutil/runner.go +++ b/op-service/testutils/kurtosisutil/runner.go @@ -17,19 +17,10 @@ func StartEnclave(t *testing.T, ctx context.Context, lgr log.Logger, pkg string, kurtosisCtx, err := kurtosis_context.NewKurtosisContextFromLocalEngine() require.NoError(t, err) - enclaveID := fmt.Sprintf("kurtosis-%s", t.Name()) + enclaveID := fmt.Sprintf("kurtosis-%s-%d", t.Name(), time.Now().UnixNano()) enclaveCtx, err := kurtosisCtx.CreateEnclave(ctx, enclaveID) require.NoError(t, err) - t.Cleanup(func() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - err = kurtosisCtx.DestroyEnclave(ctx, enclaveID) - if err != nil { - lgr.Error("Error destroying enclave", "err", err) - } - }) - stream, _, err := enclaveCtx.RunStarlarkRemotePackage( ctx, pkg, @@ -39,6 +30,17 @@ func StartEnclave(t *testing.T, ctx context.Context, lgr log.Logger, pkg string, ) require.NoError(t, err) + t.Cleanup(func() { + cancelCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + err = kurtosisCtx.DestroyEnclave(cancelCtx, enclaveID) + if err != nil { + lgr.Error("Error destroying enclave", "err", err, "id", enclaveID) + return + } + lgr.Info("Enclave destroyed", "enclave", enclaveID) + }) + logKurtosisOutput := func(msg string) { lgr.Info(fmt.Sprintf("[KURTOSIS] %s", msg)) } diff --git a/op-service/testutils/metrics.go b/op-service/testutils/metrics.go index 421d32f2109c5..25edee14a0684 100644 --- a/op-service/testutils/metrics.go +++ b/op-service/testutils/metrics.go @@ -17,7 +17,7 @@ type TestDerivationMetrics struct { FnRecordChannelTimedOut func() } -func (t *TestDerivationMetrics) CountSequencedTxs(count int) { +func (t *TestDerivationMetrics) CountSequencedTxsInBlock(txns int, deposits int) { } func (t *TestDerivationMetrics) RecordSequencerBuildingDiffTime(duration time.Duration) { diff --git a/op-service/testutils/mock_eth_client.go b/op-service/testutils/mock_eth_client.go index fcb0a60c24b08..2d82966724b33 100644 --- a/op-service/testutils/mock_eth_client.go +++ b/op-service/testutils/mock_eth_client.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -16,6 +17,11 @@ type MockEthClient struct { mock.Mock } +func (m *MockEthClient) Client() *rpc.Client { + out := m.Mock.Called() + return out.Get(0).(*rpc.Client) +} + func (m *MockEthClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) { out := m.Mock.Called(hash) return *out.Get(0).(*eth.BlockInfo), out.Error(1) diff --git a/op-service/testutils/mock_interop_backend.go b/op-service/testutils/mock_interop_backend.go index 4bb300a95ff2f..af6762c204cd6 100644 --- a/op-service/testutils/mock_interop_backend.go +++ b/op-service/testutils/mock_interop_backend.go @@ -5,8 +5,6 @@ import ( "github.com/stretchr/testify/mock" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -60,21 +58,21 @@ func (m *MockInteropBackend) ExpectFinalized(chainID types.ChainID, result eth.B m.Mock.On("Finalized", chainID).Once().Return(result, &err) } -func (m *MockInteropBackend) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.L1BlockRef, error) { - result := m.Mock.MethodCalled("DerivedFrom", chainID, blockHash, blockNumber) +func (m *MockInteropBackend) CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (eth.L1BlockRef, error) { + result := m.Mock.MethodCalled("CrossDerivedFrom", chainID, derived) return result.Get(0).(eth.L1BlockRef), *result.Get(1).(*error) } -func (m *MockInteropBackend) ExpectDerivedFrom(chainID types.ChainID, blockHash common.Hash, blockNumber uint64, result eth.L1BlockRef, err error) { - m.Mock.On("DerivedFrom", chainID, blockHash, blockNumber).Once().Return(result, &err) +func (m *MockInteropBackend) ExpectDerivedFrom(chainID types.ChainID, derived eth.BlockID, result eth.L1BlockRef, err error) { + m.Mock.On("CrossDerivedFrom", chainID, derived).Once().Return(result, &err) } -func (m *MockInteropBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.L2BlockRef) error { +func (m *MockInteropBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error { result := m.Mock.MethodCalled("UpdateLocalUnsafe", chainID, head) return *result.Get(0).(*error) } -func (m *MockInteropBackend) ExpectUpdateLocalUnsafe(chainID types.ChainID, head eth.L2BlockRef, err error) { +func (m *MockInteropBackend) ExpectUpdateLocalUnsafe(chainID types.ChainID, head eth.BlockRef, err error) { m.Mock.On("UpdateLocalUnsafe", chainID, head).Once().Return(&err) } @@ -82,12 +80,12 @@ func (m *MockInteropBackend) ExpectAnyUpdateLocalUnsafe(chainID types.ChainID, e m.Mock.On("UpdateLocalUnsafe", chainID, mock.Anything).Once().Return(&err) } -func (m *MockInteropBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef) error { +func (m *MockInteropBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error { result := m.Mock.MethodCalled("UpdateLocalSafe", chainID, derivedFrom, lastDerived) return *result.Get(0).(*error) } -func (m *MockInteropBackend) ExpectUpdateLocalSafe(chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.L2BlockRef, err error) { +func (m *MockInteropBackend) ExpectUpdateLocalSafe(chainID types.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef, err error) { m.Mock.On("UpdateLocalSafe", chainID, derivedFrom, lastDerived).Once().Return(&err) } diff --git a/op-service/testutils/random.go b/op-service/testutils/random.go index a3021ad463b65..b30d5c179bc1a 100644 --- a/op-service/testutils/random.go +++ b/op-service/testutils/random.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" ) func RandomBool(rng *rand.Rand) bool { @@ -225,6 +226,30 @@ func RandomDynamicFeeTx(rng *rand.Rand, signer types.Signer) *types.Transaction return RandomDynamicFeeTxWithBaseFee(rng, baseFee, signer) } +func RandomBlobTx(rng *rand.Rand, signer types.Signer) *types.Transaction { + baseFee := new(big.Int).SetUint64(rng.Uint64()) + key := InsecureRandomKey(rng) + tip := big.NewInt(rng.Int63n(10 * params.GWei)) + txData := &types.BlobTx{ + ChainID: uint256.MustFromBig(signer.ChainID()), + Nonce: rng.Uint64(), + GasTipCap: uint256.MustFromBig(tip), + GasFeeCap: uint256.MustFromBig(new(big.Int).Add(baseFee, tip)), + Gas: params.TxGas + uint64(rng.Int63n(2_000_000)), + To: RandomAddress(rng), + Value: uint256.MustFromBig(RandomETH(rng, 10)), + Data: RandomData(rng, rng.Intn(1000)), + AccessList: nil, + BlobFeeCap: uint256.MustFromBig(baseFee), + BlobHashes: []common.Hash{RandomHash(rng)}, + } + tx, err := types.SignNewTx(key, signer, txData) + if err != nil { + panic(err) + } + return tx +} + func RandomReceipt(rng *rand.Rand, signer types.Signer, tx *types.Transaction, txIndex uint64, cumulativeGasUsed uint64) *types.Receipt { gasUsed := params.TxGas + uint64(rng.Int63n(int64(tx.Gas()-params.TxGas+1))) logs := make([]*types.Log, rng.Intn(10)) diff --git a/op-service/tls/cli.go b/op-service/tls/cli.go index 1d5f59ae3616d..f85de807a83ee 100644 --- a/op-service/tls/cli.go +++ b/op-service/tls/cli.go @@ -12,26 +12,28 @@ import ( ) const ( - TLSCaCertFlagName = "tls.ca" - TLSCertFlagName = "tls.cert" - TLSKeyFlagName = "tls.key" + TLSCaCertFlagName = "tls.ca" + TLSCertFlagName = "tls.cert" + TLSKeyFlagName = "tls.key" + TLSEnabledFlagName = "tls.enabled" ) // CLIFlags returns flags with env var envPrefix // This should be used for server TLS configs, or when client and server tls configs are the same func CLIFlags(envPrefix string) []cli.Flag { - return CLIFlagsWithFlagPrefix(envPrefix, "") + return CLIFlagsWithFlagPrefix(envPrefix, "", "") } var ( - defaultTLSCaCert = "tls/ca.crt" - defaultTLSCert = "tls/tls.crt" - defaultTLSKey = "tls/tls.key" + defaultTLSCaCert = "tls/ca.crt" + defaultTLSCert = "tls/tls.crt" + defaultTLSKey = "tls/tls.key" + defaultTLSEnabled = true ) // CLIFlagsWithFlagPrefix returns flags with env var and cli flag prefixes // Should be used for client TLS configs when different from server on the same process -func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string) []cli.Flag { +func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string, category string) []cli.Flag { prefixFunc := func(flagName string) string { return strings.Trim(fmt.Sprintf("%s.%s", flagPrefix, flagName), ".") } @@ -39,23 +41,32 @@ func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string) []cli.Flag { return opservice.PrefixEnvVar(envPrefix, name) } return []cli.Flag{ + &cli.BoolFlag{ + Name: prefixFunc(TLSEnabledFlagName), + Usage: "Enable or disable TLS client authentication for the signer", + Value: defaultTLSEnabled, + EnvVars: prefixEnvVars("TLS_ENABLED"), + }, &cli.StringFlag{ - Name: prefixFunc(TLSCaCertFlagName), - Usage: "tls ca cert path", - Value: defaultTLSCaCert, - EnvVars: prefixEnvVars("TLS_CA"), + Name: prefixFunc(TLSCaCertFlagName), + Usage: "tls ca cert path", + Value: defaultTLSCaCert, + EnvVars: prefixEnvVars("TLS_CA"), + Category: category, }, &cli.StringFlag{ - Name: prefixFunc(TLSCertFlagName), - Usage: "tls cert path", - Value: defaultTLSCert, - EnvVars: prefixEnvVars("TLS_CERT"), + Name: prefixFunc(TLSCertFlagName), + Usage: "tls cert path", + Value: defaultTLSCert, + EnvVars: prefixEnvVars("TLS_CERT"), + Category: category, }, &cli.StringFlag{ - Name: prefixFunc(TLSKeyFlagName), - Usage: "tls key", - Value: defaultTLSKey, - EnvVars: prefixEnvVars("TLS_KEY"), + Name: prefixFunc(TLSKeyFlagName), + Usage: "tls key", + Value: defaultTLSKey, + EnvVars: prefixEnvVars("TLS_KEY"), + Category: category, }, } } @@ -64,6 +75,7 @@ type CLIConfig struct { TLSCaCert string TLSCert string TLSKey string + Enabled bool } func NewCLIConfig() CLIConfig { @@ -71,6 +83,7 @@ func NewCLIConfig() CLIConfig { TLSCaCert: defaultTLSCaCert, TLSCert: defaultTLSCert, TLSKey: defaultTLSKey, + Enabled: true, } } @@ -83,7 +96,7 @@ func (c CLIConfig) Check() error { } func (c CLIConfig) TLSEnabled() bool { - return !(c.TLSCaCert == "" && c.TLSCert == "" && c.TLSKey == "") + return c.Enabled } // ReadCLIConfig reads tls cli configs @@ -93,6 +106,7 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig { TLSCaCert: ctx.String(TLSCaCertFlagName), TLSCert: ctx.String(TLSCertFlagName), TLSKey: ctx.String(TLSKeyFlagName), + Enabled: ctx.Bool(TLSEnabledFlagName), } } @@ -106,5 +120,6 @@ func ReadCLIConfigWithPrefix(ctx *cli.Context, flagPrefix string) CLIConfig { TLSCaCert: ctx.String(prefixFunc(TLSCaCertFlagName)), TLSCert: ctx.String(prefixFunc(TLSCertFlagName)), TLSKey: ctx.String(prefixFunc(TLSKeyFlagName)), + Enabled: ctx.Bool(prefixFunc(TLSEnabledFlagName)), } } diff --git a/op-service/tls/cli_test.go b/op-service/tls/cli_test.go index c2d39dc3daab2..ce5e41d96c2d1 100644 --- a/op-service/tls/cli_test.go +++ b/op-service/tls/cli_test.go @@ -1,6 +1,7 @@ package tls import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -36,16 +37,23 @@ func TestInvalidConfig(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { cfg := NewCLIConfig() + cfg.Enabled = true test.configChange(&cfg) err := cfg.Check() require.ErrorContains(t, err, "all tls flags must be set if at least one is set") }) + t.Run(fmt.Sprintf("%sAllowedWhenDisabled", test.name), func(t *testing.T) { + cfg := NewCLIConfig() + cfg.Enabled = false + test.configChange(&cfg) + require.NoError(t, cfg.Check()) + }) } } func configForArgs(args ...string) CLIConfig { app := cli.NewApp() - app.Flags = CLIFlagsWithFlagPrefix("TEST_", "test") + app.Flags = CLIFlagsWithFlagPrefix("TEST_", "test", "") app.Name = "test" var config CLIConfig app.Action = func(ctx *cli.Context) error { diff --git a/op-service/txmgr/cli.go b/op-service/txmgr/cli.go index 2390933d79ca5..fc1aa2cceb0c9 100644 --- a/op-service/txmgr/cli.go +++ b/op-service/txmgr/cli.go @@ -191,7 +191,7 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl Value: defaults.ReceiptQueryInterval, EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"), }, - }, opsigner.CLIFlags(envPrefix)...) + }, opsigner.CLIFlags(envPrefix, "")...) } type CLIConfig struct { diff --git a/op-service/txmgr/queue.go b/op-service/txmgr/queue.go index ee7a03ffa9288..c48687d459ece 100644 --- a/op-service/txmgr/queue.go +++ b/op-service/txmgr/queue.go @@ -51,17 +51,36 @@ func (q *Queue[T]) Wait() error { return q.group.Wait() } +// handleResponse will wait for the response on the first passed channel, +// and then forward it on the second passed channel (attaching the id). It returns +// the response error or the context error if the context is canceled. +func handleResponse[T any](ctx context.Context, c chan SendResponse, d chan TxReceipt[T], id T) error { + select { + case response := <-c: + d <- TxReceipt[T]{ID: id, Receipt: response.Receipt, Err: response.Err} + return response.Err + case <-ctx.Done(): + d <- TxReceipt[T]{ID: id, Err: ctx.Err()} + return ctx.Err() + } +} + // Send will wait until the number of pending txs is below the max pending, -// and then send the next tx. +// and then send the next tx asynchronously. The nonce of the transaction is +// determined synchronously, so transactions should be confirmed on chain in +// the order they are sent using this method. // // The actual tx sending is non-blocking, with the receipt returned on the // provided receipt channel. If the channel is unbuffered, the goroutine is // blocked from completing until the channel is read from. func (q *Queue[T]) Send(id T, candidate TxCandidate, receiptCh chan TxReceipt[T]) { group, ctx := q.groupContext() - group.Go(func() error { - return q.sendTx(ctx, id, candidate, receiptCh) - }) + responseChan := make(chan SendResponse, 1) + handleResponse := func() error { + return handleResponse(ctx, responseChan, receiptCh, id) + } + group.Go(handleResponse) // This blocks until the number of handlers is below the limit + q.txMgr.SendAsync(ctx, candidate, responseChan) // Nonce management handled synchronously, i.e. before this returns } // TrySend sends the next tx, but only if the number of pending txs is below the @@ -75,19 +94,17 @@ func (q *Queue[T]) Send(id T, candidate TxCandidate, receiptCh chan TxReceipt[T] // blocked from completing until the channel is read from. func (q *Queue[T]) TrySend(id T, candidate TxCandidate, receiptCh chan TxReceipt[T]) bool { group, ctx := q.groupContext() - return group.TryGo(func() error { - return q.sendTx(ctx, id, candidate, receiptCh) - }) -} - -func (q *Queue[T]) sendTx(ctx context.Context, id T, candidate TxCandidate, receiptCh chan TxReceipt[T]) error { - receipt, err := q.txMgr.Send(ctx, candidate) - receiptCh <- TxReceipt[T]{ - ID: id, - Receipt: receipt, - Err: err, + responseChan := make(chan SendResponse, 1) + handleResponse := func() error { + return handleResponse(ctx, responseChan, receiptCh, id) + } + ok := group.TryGo(handleResponse) + if !ok { + return false + } else { + q.txMgr.SendAsync(ctx, candidate, responseChan) + return true } - return err } // groupContext returns a Group and a Context to use when sending a tx. diff --git a/op-service/txmgr/queue_test.go b/op-service/txmgr/queue_test.go index 549142c8592ac..27dce154bcc81 100644 --- a/op-service/txmgr/queue_test.go +++ b/op-service/txmgr/queue_test.go @@ -58,12 +58,15 @@ func (b *mockBackendWithNonce) NonceAt(ctx context.Context, account common.Addre func TestQueue_Send(t *testing.T) { testCases := []struct { - name string // name of the test - max uint64 // max concurrency of the queue - calls []queueCall // calls to the queue - txs []testTx // txs to generate from the factory (and potentially error in send) - nonces []uint64 // expected sent tx nonces after all calls are made - total time.Duration // approx. total time it should take to complete all queue calls + name string // name of the test + max uint64 // max concurrency of the queue + calls []queueCall // calls to the queue + txs []testTx // txs to generate from the factory (and potentially error in send) + nonces []uint64 // expected sent tx nonces after all calls are made + // With Holocene, it is important that transactions are included on chain in the same order as they are sent. + // The txmgr.Queue.Send() method should ensure nonces are determined _synchronously_ even if transactions + // are otherwise launched asynchronously. + confirmedIds []uint // expected tx Ids after all calls are made }{ { name: "success", @@ -76,8 +79,8 @@ func TestQueue_Send(t *testing.T) { {}, {}, }, - nonces: []uint64{0, 1}, - total: 1 * time.Second, + nonces: []uint64{0, 1}, + confirmedIds: []uint{0, 1}, }, { name: "no limit", @@ -90,8 +93,8 @@ func TestQueue_Send(t *testing.T) { {}, {}, }, - nonces: []uint64{0, 1}, - total: 1 * time.Second, + nonces: []uint64{0, 1}, + confirmedIds: []uint{0, 1}, }, { name: "single threaded", @@ -104,8 +107,8 @@ func TestQueue_Send(t *testing.T) { txs: []testTx{ {}, }, - nonces: []uint64{0}, - total: 1 * time.Second, + nonces: []uint64{0}, + confirmedIds: []uint{0}, }, { name: "single threaded blocking", @@ -121,8 +124,8 @@ func TestQueue_Send(t *testing.T) { {}, {}, }, - nonces: []uint64{0, 1, 2}, - total: 3 * time.Second, + nonces: []uint64{0, 1, 2}, + confirmedIds: []uint{0, 2, 3}, }, { name: "dual threaded blocking", @@ -142,8 +145,8 @@ func TestQueue_Send(t *testing.T) { {}, {}, }, - nonces: []uint64{0, 1, 2, 3, 4}, - total: 3 * time.Second, + nonces: []uint64{0, 1, 2, 3, 4}, + confirmedIds: []uint{0, 1, 3, 4, 5}, }, { name: "subsequent txs fail after tx failure", @@ -158,8 +161,8 @@ func TestQueue_Send(t *testing.T) { {sendErr: true}, {}, }, - nonces: []uint64{0, 1}, - total: 1 * time.Second, + nonces: []uint64{0, 1}, + confirmedIds: []uint{0}, }, } for _, test := range testCases { @@ -183,9 +186,11 @@ func TestQueue_Send(t *testing.T) { // track the nonces, and return any expected errors from tx sending var ( - nonces []uint64 - nonceMu sync.Mutex + nonces []uint64 + nonceForTxId map[uint]uint64 // maps from txid to nonce + nonceMu sync.Mutex ) + nonceForTxId = make(map[uint]uint64) sendTx := func(ctx context.Context, tx *types.Transaction) error { index := int(tx.Data()[0]) nonceMu.Lock() @@ -198,8 +203,12 @@ func TestQueue_Send(t *testing.T) { if testTx != nil && testTx.sendErr { return core.ErrNonceTooLow } + txHash := tx.Hash() + nonceMu.Lock() backend.mine(&txHash, tx.GasFeeCap(), nil) + nonceForTxId[uint(index)] = tx.Nonce() + nonceMu.Unlock() return nil } backend.setTxSender(sendTx) @@ -209,7 +218,6 @@ func TestQueue_Send(t *testing.T) { queue := NewQueue[int](ctx, mgr, test.max) // make all the queue calls given in the test case - start := time.Now() receiptChs := make([]chan TxReceipt[int], len(test.calls)) for i, c := range test.calls { msg := fmt.Sprintf("Call %d", i) @@ -217,19 +225,28 @@ func TestQueue_Send(t *testing.T) { TxData: []byte{byte(i)}, To: &common.Address{}, } + if i == 0 { + // Make the first tx much larger to expose + // any race conditions in the queue + candidate.TxData = make([]byte, 100_000) + } receiptChs[i] = make(chan TxReceipt[int], 1) queued := c.call(i, candidate, receiptChs[i], queue) require.Equal(t, c.queued, queued, msg) } // wait for the queue to drain (all txs complete or failed) _ = queue.Wait() - duration := time.Since(start) - // expect the execution time within a certain window - now := time.Now() - require.WithinDuration(t, now.Add(test.total), now.Add(duration), 500*time.Millisecond, "unexpected queue transaction timing") - // check that the nonces match + + // NOTE the backend in this test does not order transactions based on the nonce + // So what we want to check is that the txs match expectations when they are ordered + // in the same way as the nonces. slices.Sort(nonces) require.Equal(t, test.nonces, nonces, "expected nonces do not match") + for i, id := range test.confirmedIds { + require.Equal(t, nonces[i], nonceForTxId[id], + "nonce for tx id %d was %d instead of %d", id, nonceForTxId[id], nonces[i]) + } + // check receipts for i, c := range test.calls { if !c.queued { diff --git a/op-service/txmgr/send_state_test.go b/op-service/txmgr/send_state_test.go index d48db5fd15144..0da3d8ba19fae 100644 --- a/op-service/txmgr/send_state_test.go +++ b/op-service/txmgr/send_state_test.go @@ -58,7 +58,7 @@ func TestSendStateNoAbortAfterProcessOtherError(t *testing.T) { require.Nil(t, sendState.CriticalError()) } -// TestSendStateAbortSafelyAfterNonceTooLowButNoTxMined asserts that we will abort after the very +// TestSendStateAbortSafelyAfterNonceTooLowNoTxPublished asserts that we will abort after the very // first none-too-low error if a tx hasn't yet been published. func TestSendStateAbortSafelyAfterNonceTooLowNoTxPublished(t *testing.T) { sendState := newSendState() diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index e633a74c28818..e6593f993759e 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -363,26 +363,32 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (* } } + // Calculate the intrinsic gas for the transaction + callMsg := ethereum.CallMsg{ + From: m.cfg.From, + To: candidate.To, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Data: candidate.TxData, + Value: candidate.Value, + } + if len(blobHashes) > 0 { + callMsg.BlobGasFeeCap = blobBaseFee + callMsg.BlobHashes = blobHashes + } // If the gas limit is set, we can use that as the gas if gasLimit == 0 { - // Calculate the intrinsic gas for the transaction - callMsg := ethereum.CallMsg{ - From: m.cfg.From, - To: candidate.To, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Data: candidate.TxData, - Value: candidate.Value, - } - if len(blobHashes) > 0 { - callMsg.BlobGasFeeCap = blobBaseFee - callMsg.BlobHashes = blobHashes - } gas, err := m.backend.EstimateGas(ctx, callMsg) if err != nil { return nil, fmt.Errorf("failed to estimate gas: %w", errutil.TryAddRevertReason(err)) } gasLimit = gas + } else { + callMsg.Gas = gasLimit + _, err := m.backend.CallContract(ctx, callMsg, nil) + if err != nil { + return nil, fmt.Errorf("failed to call: %w", errutil.TryAddRevertReason(err)) + } } var txMessage types.TxData @@ -600,7 +606,7 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t func (m *SimpleTxManager) publishTx(ctx context.Context, tx *types.Transaction, sendState *SendState) (*types.Transaction, bool) { l := m.txLogger(tx, true) - l.Info("Publishing transaction", "tx", tx.Hash()) + l.Info("Publishing transaction") for { if sendState.bumpFees { @@ -797,14 +803,30 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa } // Re-estimate gaslimit in case things have changed or a previous gaslimit estimate was wrong - gas, err := m.backend.EstimateGas(ctx, ethereum.CallMsg{ + callMsg := ethereum.CallMsg{ From: m.cfg.From, To: tx.To(), GasTipCap: bumpedTip, GasFeeCap: bumpedFee, Data: tx.Data(), Value: tx.Value(), - }) + } + var bumpedBlobFee *big.Int + if tx.Type() == types.BlobTxType { + // Blob transactions have an additional blob gas price we must specify, so we must make sure it is + // getting bumped appropriately. + bumpedBlobFee = calcThresholdValue(tx.BlobGasFeeCap(), true) + if bumpedBlobFee.Cmp(blobBaseFee) < 0 { + bumpedBlobFee = blobBaseFee + } + if err := m.checkBlobFeeLimits(blobBaseFee, bumpedBlobFee); err != nil { + return nil, err + } + + callMsg.BlobGasFeeCap = bumpedBlobFee + callMsg.BlobHashes = tx.BlobHashes() + } + gas, err := m.backend.EstimateGas(ctx, callMsg) if err != nil { // If this is a transaction resubmission, we sometimes see this outcome because the // original tx can get included in a block just before the above call. In this case the @@ -830,15 +852,6 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa var newTx *types.Transaction if tx.Type() == types.BlobTxType { - // Blob transactions have an additional blob gas price we must specify, so we must make sure it is - // getting bumped appropriately. - bumpedBlobFee := calcThresholdValue(tx.BlobGasFeeCap(), true) - if bumpedBlobFee.Cmp(blobBaseFee) < 0 { - bumpedBlobFee = blobBaseFee - } - if err := m.checkBlobFeeLimits(blobBaseFee, bumpedBlobFee); err != nil { - return nil, err - } message := &types.BlobTx{ Nonce: tx.Nonce(), To: *tx.To(), diff --git a/op-supervisor/Makefile b/op-supervisor/Makefile index de4f2d9d26126..144f7abd606e8 100644 --- a/op-supervisor/Makefile +++ b/op-supervisor/Makefile @@ -1,23 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -VERSION ?= v0.0.0 +DEPRECATED_TARGETS := op-supervisor clean test -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.Version=$(VERSION) -LDFLAGSSTRING +=-X main.Meta=$(VERSION_META) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -op-supervisor: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-supervisor ./cmd - -clean: - rm bin/op-supervisor - -test: - go test -v ./... - -.PHONY: \ - op-supervisor \ - clean \ - test +include ../just/deprecated.mk diff --git a/op-supervisor/cmd/main.go b/op-supervisor/cmd/main.go index 8e306bf9009de..5aec4e927d031 100644 --- a/op-supervisor/cmd/main.go +++ b/op-supervisor/cmd/main.go @@ -20,7 +20,7 @@ import ( ) var ( - Version = "v0.0.1" + Version = "v0.0.0" GitCommit = "" GitDate = "" ) diff --git a/op-supervisor/cmd/main_test.go b/op-supervisor/cmd/main_test.go index 6a463a81275aa..c61b6f3cb1ca1 100644 --- a/op-supervisor/cmd/main_test.go +++ b/op-supervisor/cmd/main_test.go @@ -6,12 +6,13 @@ import ( "fmt" "testing" - "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" ) var ( @@ -36,7 +37,8 @@ func TestLogLevel(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { cfg := configForArgs(t, addRequiredArgs()) - defaultCfgTempl := config.NewConfig(ValidL2RPCs, ValidDatadir) + depSet := &depset.JsonDependencySetLoader{Path: "test"} + defaultCfgTempl := config.NewConfig(ValidL2RPCs, depSet, ValidDatadir) defaultCfg := *defaultCfgTempl defaultCfg.Version = Version require.Equal(t, defaultCfg, *cfg) @@ -123,8 +125,9 @@ func toArgList(req map[string]string) []string { func requiredArgs() map[string]string { args := map[string]string{ - "--l2-rpcs": ValidL2RPCs[0], - "--datadir": ValidDatadir, + "--l2-rpcs": ValidL2RPCs[0], + "--dependency-set": "test", + "--datadir": ValidDatadir, } return args } diff --git a/op-supervisor/config/config.go b/op-supervisor/config/config.go index dbf723d55d3b4..b06d0592593bd 100644 --- a/op-supervisor/config/config.go +++ b/op-supervisor/config/config.go @@ -7,11 +7,13 @@ import ( opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" ) var ( - ErrMissingL2RPC = errors.New("must specify at least one L2 RPC") - ErrMissingDatadir = errors.New("must specify datadir") + ErrMissingL2RPC = errors.New("must specify at least one L2 RPC") + ErrMissingDependencySet = errors.New("must specify a dependency set source") + ErrMissingDatadir = errors.New("must specify datadir") ) type Config struct { @@ -22,9 +24,15 @@ type Config struct { PprofConfig oppprof.CLIConfig RPC oprpc.CLIConfig + DependencySetSource depset.DependencySetSource + // MockRun runs the service with a mock backend MockRun bool + // SynchronousProcessors disables background-workers, + // requiring manual triggers for the backend to process anything. + SynchronousProcessors bool + L2RPCs []string Datadir string } @@ -37,6 +45,9 @@ func (c *Config) Check() error { if len(c.L2RPCs) == 0 { result = errors.Join(result, ErrMissingL2RPC) } + if c.DependencySetSource == nil { + result = errors.Join(result, ErrMissingDependencySet) + } if c.Datadir == "" { result = errors.Join(result, ErrMissingDatadir) } @@ -45,14 +56,15 @@ func (c *Config) Check() error { // NewConfig creates a new config using default values whenever possible. // Required options with no suitable default are passed as parameters. -func NewConfig(l2RPCs []string, datadir string) *Config { +func NewConfig(l2RPCs []string, depSet depset.DependencySetSource, datadir string) *Config { return &Config{ - LogConfig: oplog.DefaultCLIConfig(), - MetricsConfig: opmetrics.DefaultCLIConfig(), - PprofConfig: oppprof.DefaultCLIConfig(), - RPC: oprpc.DefaultCLIConfig(), - MockRun: false, - L2RPCs: l2RPCs, - Datadir: datadir, + LogConfig: oplog.DefaultCLIConfig(), + MetricsConfig: opmetrics.DefaultCLIConfig(), + PprofConfig: oppprof.DefaultCLIConfig(), + RPC: oprpc.DefaultCLIConfig(), + DependencySetSource: depSet, + MockRun: false, + L2RPCs: l2RPCs, + Datadir: datadir, } } diff --git a/op-supervisor/config/config_test.go b/op-supervisor/config/config_test.go index ef84e4be81bd7..0d354ca134a56 100644 --- a/op-supervisor/config/config_test.go +++ b/op-supervisor/config/config_test.go @@ -3,10 +3,13 @@ package config import ( "testing" + "github.com/stretchr/testify/require" + "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" "github.com/ethereum-optimism/optimism/op-service/rpc" - "github.com/stretchr/testify/require" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) func TestDefaultConfigIsValid(t *testing.T) { @@ -20,6 +23,12 @@ func TestRequireL2RPC(t *testing.T) { require.ErrorIs(t, cfg.Check(), ErrMissingL2RPC) } +func TestRequireDependencySet(t *testing.T) { + cfg := validConfig() + cfg.DependencySetSource = nil + require.ErrorIs(t, cfg.Check(), ErrMissingDependencySet) +} + func TestRequireDatadir(t *testing.T) { cfg := validConfig() cfg.Datadir = "" @@ -47,6 +56,16 @@ func TestValidateRPCConfig(t *testing.T) { } func validConfig() *Config { + depSet, err := depset.NewStaticConfigDependencySet(map[types.ChainID]*depset.StaticConfigDependency{ + types.ChainIDFromUInt64(900): &depset.StaticConfigDependency{ + ChainIndex: 900, + ActivationTime: 0, + HistoryMinTime: 0, + }, + }) + if err != nil { + panic(err) + } // Should be valid using only the required arguments passed in via the constructor. - return NewConfig([]string{"http://localhost:8545"}, "./supervisor_config_testdir") + return NewConfig([]string{"http://localhost:8545"}, depSet, "./supervisor_testdir") } diff --git a/op-supervisor/flags/flags.go b/op-supervisor/flags/flags.go index 1759381694acb..9b49823f304a5 100644 --- a/op-supervisor/flags/flags.go +++ b/op-supervisor/flags/flags.go @@ -3,7 +3,6 @@ package flags import ( "fmt" - "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/urfave/cli/v2" opservice "github.com/ethereum-optimism/optimism/op-service" @@ -11,6 +10,8 @@ import ( opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" ) const EnvVarPrefix = "OP_SUPERVISOR" @@ -30,6 +31,12 @@ var ( Usage: "Directory to store data generated as part of responding to games", EnvVars: prefixEnvVars("DATADIR"), } + DependencySetFlag = &cli.PathFlag{ + Name: "dependency-set", + Usage: "Dependency-set configuration, point at JSON file.", + EnvVars: prefixEnvVars("DEPENDENCY_SET"), + TakesFile: true, + } MockRunFlag = &cli.BoolFlag{ Name: "mock-run", Usage: "Mock run, no actual backend used, just presenting the service", @@ -41,6 +48,7 @@ var ( var requiredFlags = []cli.Flag{ L2RPCsFlag, DataDirFlag, + DependencySetFlag, } var optionalFlags = []cli.Flag{ @@ -71,13 +79,14 @@ func CheckRequired(ctx *cli.Context) error { func ConfigFromCLI(ctx *cli.Context, version string) *config.Config { return &config.Config{ - Version: version, - LogConfig: oplog.ReadCLIConfig(ctx), - MetricsConfig: opmetrics.ReadCLIConfig(ctx), - PprofConfig: oppprof.ReadCLIConfig(ctx), - RPC: oprpc.ReadCLIConfig(ctx), - MockRun: ctx.Bool(MockRunFlag.Name), - L2RPCs: ctx.StringSlice(L2RPCsFlag.Name), - Datadir: ctx.Path(DataDirFlag.Name), + Version: version, + LogConfig: oplog.ReadCLIConfig(ctx), + MetricsConfig: opmetrics.ReadCLIConfig(ctx), + PprofConfig: oppprof.ReadCLIConfig(ctx), + RPC: oprpc.ReadCLIConfig(ctx), + DependencySetSource: &depset.JsonDependencySetLoader{Path: ctx.Path(DependencySetFlag.Name)}, + MockRun: ctx.Bool(MockRunFlag.Name), + L2RPCs: ctx.StringSlice(L2RPCsFlag.Name), + Datadir: ctx.Path(DataDirFlag.Name), } } diff --git a/op-supervisor/justfile b/op-supervisor/justfile new file mode 100644 index 0000000000000..7063a3ffb05e3 --- /dev/null +++ b/op-supervisor/justfile @@ -0,0 +1,21 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X main.Version=" + VERSION + " " + \ + "-X main.Meta=" + VERSION_META + " " + \ + "") + "'" + +BINARY := "./bin/op-supervisor" + +# Build op-supervisor binary +op-supervisor: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") diff --git a/op-supervisor/metrics/metrics.go b/op-supervisor/metrics/metrics.go index e025d509ee287..0e6ca9a8da2c9 100644 --- a/op-supervisor/metrics/metrics.go +++ b/op-supervisor/metrics/metrics.go @@ -1,10 +1,10 @@ package metrics import ( - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/prometheus/client_golang/prometheus" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) const Namespace = "op_supervisor" @@ -18,7 +18,7 @@ type Metricer interface { CacheAdd(chainID types.ChainID, label string, cacheSize int, evicted bool) CacheGet(chainID types.ChainID, label string, hit bool) - RecordDBEntryCount(chainID types.ChainID, count int64) + RecordDBEntryCount(chainID types.ChainID, kind string, count int64) RecordDBSearchEntriesRead(chainID types.ChainID, count int64) Document() []opmetrics.DocumentedMetric @@ -106,9 +106,10 @@ func NewMetrics(procName string) *Metrics { DBEntryCountVec: factory.NewGaugeVec(prometheus.GaugeOpts{ Namespace: ns, Name: "logdb_entries_current", - Help: "Current number of entries in the log database by chain ID", + Help: "Current number of entries in the database of specified kind and chain ID", }, []string{ "chain", + "kind", }), DBSearchEntriesReadVec: factory.NewHistogramVec(prometheus.HistogramOpts{ Namespace: ns, @@ -159,8 +160,8 @@ func (m *Metrics) CacheGet(chainID types.ChainID, label string, hit bool) { } } -func (m *Metrics) RecordDBEntryCount(chainID types.ChainID, count int64) { - m.DBEntryCountVec.WithLabelValues(chainIDLabel(chainID)).Set(float64(count)) +func (m *Metrics) RecordDBEntryCount(chainID types.ChainID, kind string, count int64) { + m.DBEntryCountVec.WithLabelValues(chainIDLabel(chainID), kind).Set(float64(count)) } func (m *Metrics) RecordDBSearchEntriesRead(chainID types.ChainID, count int64) { diff --git a/op-supervisor/metrics/noop.go b/op-supervisor/metrics/noop.go index 7fad61d4c15a1..7f162f6dc2b86 100644 --- a/op-supervisor/metrics/noop.go +++ b/op-supervisor/metrics/noop.go @@ -19,5 +19,5 @@ func (*noopMetrics) RecordUp() {} func (m *noopMetrics) CacheAdd(_ types.ChainID, _ string, _ int, _ bool) {} func (m *noopMetrics) CacheGet(_ types.ChainID, _ string, _ bool) {} -func (m *noopMetrics) RecordDBEntryCount(_ types.ChainID, _ int64) {} -func (m *noopMetrics) RecordDBSearchEntriesRead(_ types.ChainID, _ int64) {} +func (m *noopMetrics) RecordDBEntryCount(_ types.ChainID, _ string, _ int64) {} +func (m *noopMetrics) RecordDBSearchEntriesRead(_ types.ChainID, _ int64) {} diff --git a/op-supervisor/supervisor/backend/backend.go b/op-supervisor/supervisor/backend/backend.go index 563268ae3f3f4..7d368cb40f159 100644 --- a/op-supervisor/supervisor/backend/backend.go +++ b/op-supervisor/supervisor/backend/backend.go @@ -4,21 +4,22 @@ import ( "context" "errors" "fmt" - "io" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/locks" + "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/cross" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/source" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/processors" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -29,85 +30,207 @@ type SupervisorBackend struct { m Metrics dataDir string - chainMonitors map[types.ChainID]*source.ChainMonitor - db *db.ChainsDB + // depSet is the dependency set that the backend uses to know about the chains it is indexing + depSet depset.DependencySet + + // chainDBs holds on to the DB indices for each chain + chainDBs *db.ChainsDB + + // chainProcessors are notified of new unsafe blocks, and add the unsafe log events data into the events DB + chainProcessors locks.RWMap[types.ChainID, *processors.ChainProcessor] + + // crossSafeProcessors take local-safe data and promote it to cross-safe when verified + crossSafeProcessors locks.RWMap[types.ChainID, *cross.Worker] + + // crossUnsafeProcessors take local-unsafe data and promote it to cross-unsafe when verified + crossUnsafeProcessors locks.RWMap[types.ChainID, *cross.Worker] + + // chainMetrics are used to track metrics for each chain + // they are reused for processors and databases of the same chain + chainMetrics locks.RWMap[types.ChainID, *chainMetrics] + + // synchronousProcessors disables background-workers, + // requiring manual triggers for the backend to process anything. + synchronousProcessors bool } var _ frontend.Backend = (*SupervisorBackend)(nil) -var _ io.Closer = (*SupervisorBackend)(nil) +var errAlreadyStopped = errors.New("already stopped") func NewSupervisorBackend(ctx context.Context, logger log.Logger, m Metrics, cfg *config.Config) (*SupervisorBackend, error) { // attempt to prepare the data directory - if err := prepDataDir(cfg.Datadir); err != nil { + if err := db.PrepDataDir(cfg.Datadir); err != nil { return nil, err } - // create the chains db - db := db.NewChainsDB(map[types.ChainID]db.LogStorage{}, logger) + // Load the dependency set + depSet, err := cfg.DependencySetSource.LoadDependencySet(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load dependency set: %w", err) + } - // create an empty map of chain monitors - chainMonitors := make(map[types.ChainID]*source.ChainMonitor, len(cfg.L2RPCs)) + // create initial per-chain resources + chainsDBs := db.NewChainsDB(logger, depSet) // create the supervisor backend super := &SupervisorBackend{ - logger: logger, - m: m, - dataDir: cfg.Datadir, - chainMonitors: chainMonitors, - db: db, + logger: logger, + m: m, + dataDir: cfg.Datadir, + depSet: depSet, + chainDBs: chainsDBs, + // For testing we can avoid running the processors. + synchronousProcessors: cfg.SynchronousProcessors, + } + + // Initialize the resources of the supervisor backend. + // Stop the supervisor if any of the resources fails to be initialized. + if err := super.initResources(ctx, cfg); err != nil { + err = fmt.Errorf("failed to init resources: %w", err) + return nil, errors.Join(err, super.Stop(ctx)) + } + + return super, nil +} + +// initResources initializes all the resources, such as DBs and processors for chains. +// An error may returned, without closing the thus-far initialized resources. +// Upon error the caller should call Stop() on the supervisor backend to clean up and release resources. +func (su *SupervisorBackend) initResources(ctx context.Context, cfg *config.Config) error { + chains := su.depSet.Chains() + + // for each chain known to the dependency set, create the necessary DB resources + for _, chainID := range chains { + if err := su.openChainDBs(chainID); err != nil { + return fmt.Errorf("failed to open chain %s: %w", chainID, err) + } + } + + // initialize all cross-unsafe processors + for _, chainID := range chains { + worker := cross.NewCrossUnsafeWorker(su.logger, chainID, su.chainDBs) + su.crossUnsafeProcessors.Set(chainID, worker) + } + // initialize all cross-safe processors + for _, chainID := range chains { + worker := cross.NewCrossSafeWorker(su.logger, chainID, su.chainDBs) + su.crossSafeProcessors.Set(chainID, worker) + } + // For each chain initialize a chain processor service, + // after cross-unsafe workers are ready to receive updates + for _, chainID := range chains { + logProcessor := processors.NewLogProcessor(chainID, su.chainDBs) + chainProcessor := processors.NewChainProcessor(su.logger, chainID, logProcessor, su.chainDBs, su.onIndexedLocalUnsafeData) + su.chainProcessors.Set(chainID, chainProcessor) } - // from the RPC strings, have the supervisor backend create a chain monitor - // don't start the monitor yet, as we will start all monitors at once when Start is called + // the config has some RPC connections to attach to the chain-processors for _, rpc := range cfg.L2RPCs { - err := super.addFromRPC(ctx, logger, rpc, false) + err := su.attachRPC(ctx, rpc) if err != nil { - return nil, fmt.Errorf("failed to add chain monitor for rpc %v: %w", rpc, err) + return fmt.Errorf("failed to add chain monitor for rpc %v: %w", rpc, err) } } - return super, nil + return nil } -// addFromRPC adds a chain monitor to the supervisor backend from an rpc endpoint -// it does not expect to be called after the backend has been started -// it will start the monitor if shouldStart is true -func (su *SupervisorBackend) addFromRPC(ctx context.Context, logger log.Logger, rpc string, shouldStart bool) error { - // create the rpc client, which yields the chain id - rpcClient, chainID, err := createRpcClient(ctx, logger, rpc) +// onIndexedLocalUnsafeData is called by the event indexing workers. +// This signals to cross-unsafe workers that there's data to index. +func (su *SupervisorBackend) onIndexedLocalUnsafeData() { + // We signal all workers, since dependencies on a chain may be unblocked + // by new data on other chains. + // Busy workers don't block processing. + // The signal is picked up only if the worker is running in the background. + su.crossUnsafeProcessors.Range(func(_ types.ChainID, w *cross.Worker) bool { + w.OnNewData() + return true + }) +} + +// onNewLocalSafeData is called by the safety-indexing. +// This signals to cross-safe workers that there's data to index. +func (su *SupervisorBackend) onNewLocalSafeData() { + // We signal all workers, since dependencies on a chain may be unblocked + // by new data on other chains. + // Busy workers don't block processing. + // The signal is picked up only if the worker is running in the background. + su.crossSafeProcessors.Range(func(_ types.ChainID, w *cross.Worker) bool { + w.OnNewData() + return true + }) +} + +// openChainDBs initializes all the DB resources of a specific chain. +// It is a sub-task of initResources. +func (su *SupervisorBackend) openChainDBs(chainID types.ChainID) error { + cm := newChainMetrics(chainID, su.m) + // create metrics and a logdb for the chain + su.chainMetrics.Set(chainID, cm) + + logDB, err := db.OpenLogDB(su.logger, chainID, su.dataDir, cm) if err != nil { - return err + return fmt.Errorf("failed to open logDB of chain %s: %w", chainID, err) } - su.logger.Info("adding from rpc connection", "rpc", rpc, "chainID", chainID) - // create metrics and a logdb for the chain - cm := newChainMetrics(chainID, su.m) - path, err := prepLogDBPath(chainID, su.dataDir) + su.chainDBs.AddLogDB(chainID, logDB) + + localDB, err := db.OpenLocalDerivedFromDB(su.logger, chainID, su.dataDir, cm) if err != nil { - return fmt.Errorf("failed to create datadir for chain %v: %w", chainID, err) + return fmt.Errorf("failed to open local derived-from DB of chain %s: %w", chainID, err) } - logDB, err := logs.NewFromFile(logger, cm, path, true) + su.chainDBs.AddLocalDerivedFromDB(chainID, localDB) + + crossDB, err := db.OpenCrossDerivedFromDB(su.logger, chainID, su.dataDir, cm) if err != nil { - return fmt.Errorf("failed to create logdb for chain %v at %v: %w", chainID, path, err) + return fmt.Errorf("failed to open cross derived-from DB of chain %s: %w", chainID, err) } - if su.chainMonitors[chainID] != nil { - return fmt.Errorf("chain monitor for chain %v already exists", chainID) + su.chainDBs.AddCrossDerivedFromDB(chainID, crossDB) + + su.chainDBs.AddCrossUnsafeTracker(chainID) + return nil +} + +func (su *SupervisorBackend) attachRPC(ctx context.Context, rpc string) error { + su.logger.Info("attaching RPC to chain processor", "rpc", rpc) + + logger := su.logger.New("rpc", rpc) + // create the rpc client, which yields the chain id + rpcClient, chainID, err := clientForL2(ctx, logger, rpc) + if err != nil { + return err + } + if !su.depSet.HasChain(chainID) { + return fmt.Errorf("chain %s is not part of the interop dependency set: %w", chainID, types.ErrUnknownChain) } - monitor, err := source.NewChainMonitor(ctx, logger, cm, chainID, rpc, rpcClient, su.db) + cm, ok := su.chainMetrics.Get(chainID) + if !ok { + return fmt.Errorf("failed to find metrics for chain %v", chainID) + } + // create an RPC client that the processor can use + cl, err := processors.NewEthClient( + ctx, + logger.New("chain", chainID), + cm, + rpc, + rpcClient, 2*time.Second, + false, + sources.RPCKindStandard) if err != nil { - return fmt.Errorf("failed to create monitor for rpc %v: %w", rpc, err) + return err } - // start the monitor if requested - if shouldStart { - if err := monitor.Start(); err != nil { - return fmt.Errorf("failed to start monitor for rpc %v: %w", rpc, err) - } + return su.AttachProcessorSource(chainID, cl) +} + +func (su *SupervisorBackend) AttachProcessorSource(chainID types.ChainID, src processors.Source) error { + proc, ok := su.chainProcessors.Get(chainID) + if !ok { + return fmt.Errorf("unknown chain %s, cannot attach RPC to processor", chainID) } - su.chainMonitors[chainID] = monitor - su.db.AddLogDB(chainID, logDB) + proc.SetSource(src) return nil } -func createRpcClient(ctx context.Context, logger log.Logger, rpc string) (client.RPC, types.ChainID, error) { +func clientForL2(ctx context.Context, logger log.Logger, rpc string) (client.RPC, types.ChainID, error) { ethClient, err := dial.DialEthClientWithTimeout(ctx, 10*time.Second, logger, rpc) if err != nil { return nil, types.ChainID{}, fmt.Errorf("failed to connect to rpc %v: %w", rpc, err) @@ -124,79 +247,116 @@ func (su *SupervisorBackend) Start(ctx context.Context) error { if !su.started.CompareAndSwap(false, true) { return errors.New("already started") } + // initiate "ResumeFromLastSealedBlock" on the chains db, // which rewinds the database to the last block that is guaranteed to have been fully recorded - if err := su.db.ResumeFromLastSealedBlock(); err != nil { + if err := su.chainDBs.ResumeFromLastSealedBlock(); err != nil { return fmt.Errorf("failed to resume chains db: %w", err) } - // start chain monitors - for _, monitor := range su.chainMonitors { - if err := monitor.Start(); err != nil { - return fmt.Errorf("failed to start chain monitor: %w", err) - } + + if !su.synchronousProcessors { + // Make all the chain-processors run automatic background processing + su.chainProcessors.Range(func(_ types.ChainID, processor *processors.ChainProcessor) bool { + processor.StartBackground() + return true + }) + su.crossUnsafeProcessors.Range(func(_ types.ChainID, worker *cross.Worker) bool { + worker.StartBackground() + return true + }) + su.crossSafeProcessors.Range(func(_ types.ChainID, worker *cross.Worker) bool { + worker.StartBackground() + return true + }) } + return nil } -var errAlreadyStopped = errors.New("already stopped") - func (su *SupervisorBackend) Stop(ctx context.Context) error { if !su.started.CompareAndSwap(true, false) { return errAlreadyStopped } - // collect errors from stopping chain monitors - var errs error - for _, monitor := range su.chainMonitors { - if err := monitor.Stop(); err != nil { - errs = errors.Join(errs, fmt.Errorf("failed to stop chain monitor: %w", err)) - } - } - // close the database - if err := su.db.Close(); err != nil { - errs = errors.Join(errs, fmt.Errorf("failed to close database: %w", err)) - } - return errs -} + su.logger.Info("Closing supervisor backend") + // close all processors + su.chainProcessors.Range(func(id types.ChainID, processor *processors.ChainProcessor) bool { + su.logger.Info("stopping chain processor", "chainID", id) + processor.Close() + return true + }) + su.chainProcessors.Clear() -func (su *SupervisorBackend) Close() error { - // TODO(protocol-quest#288): close logdb of all chains - return nil + su.crossUnsafeProcessors.Range(func(id types.ChainID, worker *cross.Worker) bool { + su.logger.Info("stopping cross-unsafe processor", "chainID", id) + worker.Close() + return true + }) + su.crossUnsafeProcessors.Clear() + + su.crossSafeProcessors.Range(func(id types.ChainID, worker *cross.Worker) bool { + su.logger.Info("stopping cross-safe processor", "chainID", id) + worker.Close() + return true + }) + su.crossSafeProcessors.Clear() + + // close the databases + return su.chainDBs.Close() } -// AddL2RPC adds a new L2 chain to the supervisor backend -// it stops and restarts the backend to add the new chain +// AddL2RPC attaches an RPC as the RPC for the given chain, overriding the previous RPC source, if any. func (su *SupervisorBackend) AddL2RPC(ctx context.Context, rpc string) error { - // start the monitor immediately, as the backend is assumed to already be running - return su.addFromRPC(ctx, su.logger, rpc, true) + return su.attachRPC(ctx, rpc) } +// Internal methods, for processors +// ---------------------------- + +func (su *SupervisorBackend) DependencySet() depset.DependencySet { + return su.depSet +} + +// Query methods +// ---------------------------- + func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { + logHash := types.PayloadHashToLogHash(payloadHash, identifier.Origin) chainID := identifier.ChainID blockNum := identifier.BlockNumber logIdx := identifier.LogIndex - _, err := su.db.Check(chainID, blockNum, uint32(logIdx), payloadHash) - if errors.Is(err, logs.ErrFuture) { + _, err := su.chainDBs.Check(chainID, blockNum, logIdx, logHash) + if errors.Is(err, types.ErrFuture) { + su.logger.Debug("Future message", "identifier", identifier, "payloadHash", payloadHash, "err", err) return types.LocalUnsafe, nil } - if errors.Is(err, logs.ErrConflict) { + if errors.Is(err, types.ErrConflict) { + su.logger.Debug("Conflicting message", "identifier", identifier, "payloadHash", payloadHash, "err", err) return types.Invalid, nil } if err != nil { return types.Invalid, fmt.Errorf("failed to check log: %w", err) } - safest := su.db.Safest(chainID, blockNum, uint32(logIdx)) - return safest, nil + return su.chainDBs.Safest(chainID, blockNum, logIdx) } func (su *SupervisorBackend) CheckMessages( messages []types.Message, minSafety types.SafetyLevel) error { + su.logger.Debug("Checking messages", "count", len(messages), "minSafety", minSafety) + for _, msg := range messages { + su.logger.Debug("Checking message", + "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String()) safety, err := su.CheckMessage(msg.Identifier, msg.PayloadHash) if err != nil { + su.logger.Error("Check message failed", "err", err, + "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String()) return fmt.Errorf("failed to check message: %w", err) } if !safety.AtLeastAsSafe(minSafety) { + su.logger.Error("Message is not sufficiently safe", + "safety", safety, "minSafety", minSafety, + "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String()) return fmt.Errorf("message %v (safety level: %v) does not meet the minimum safety %v", msg.Identifier, safety, @@ -206,32 +366,106 @@ func (su *SupervisorBackend) CheckMessages( return nil } -// CheckBlock checks if the block is safe according to the safety level -// The block is considered safe if all logs in the block are safe -// this is decided by finding the last log in the block and -func (su *SupervisorBackend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) { - // find the last log index in the block - id := eth.BlockID{Hash: blockHash, Number: uint64(blockNumber)} - _, err := su.db.FindSealedBlock(types.ChainID(*chainID), id) - if errors.Is(err, logs.ErrFuture) { - return types.LocalUnsafe, nil +func (su *SupervisorBackend) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) { + head, err := su.chainDBs.LocalUnsafe(chainID) + if err != nil { + return types.ReferenceView{}, fmt.Errorf("failed to get local-unsafe head: %w", err) } - if errors.Is(err, logs.ErrConflict) { - return types.Invalid, nil + cross, err := su.chainDBs.CrossUnsafe(chainID) + if err != nil { + return types.ReferenceView{}, fmt.Errorf("failed to get cross-unsafe head: %w", err) + } + + // TODO(#11693): check `unsafe` input to detect reorg conflicts + + return types.ReferenceView{ + Local: head.ID(), + Cross: cross.ID(), + }, nil +} + +func (su *SupervisorBackend) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) { + _, localSafe, err := su.chainDBs.LocalSafe(chainID) + if err != nil { + return types.ReferenceView{}, fmt.Errorf("failed to get local-safe head: %w", err) } + _, crossSafe, err := su.chainDBs.CrossSafe(chainID) if err != nil { - su.logger.Error("failed to scan block", "err", err) - return "", err + return types.ReferenceView{}, fmt.Errorf("failed to get cross-safe head: %w", err) } - safest := su.db.Safest(types.ChainID(*chainID), uint64(blockNumber), 0) - return safest, nil + + // TODO(#11693): check `safe` input to detect reorg conflicts + + return types.ReferenceView{ + Local: localSafe.ID(), + Cross: crossSafe.ID(), + }, nil } -func (su *SupervisorBackend) DerivedFrom( - ctx context.Context, - chainID types.ChainID, - blockHash common.Hash, - blockNumber uint64) (eth.BlockRef, error) { - // TODO(#12358): attach to backend - return eth.BlockRef{}, nil +func (su *SupervisorBackend) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) { + v, err := su.chainDBs.Finalized(chainID) + if err != nil { + return eth.BlockID{}, err + } + return v.ID(), nil +} + +func (su *SupervisorBackend) CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) { + v, err := su.chainDBs.CrossDerivedFromBlockRef(chainID, derived) + if err != nil { + return eth.BlockRef{}, err + } + return v, nil +} + +// Update methods +// ---------------------------- + +func (su *SupervisorBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error { + ch, ok := su.chainProcessors.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + return ch.OnNewHead(head) +} + +func (su *SupervisorBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) error { + err := su.chainDBs.UpdateLocalSafe(chainID, derivedFrom, lastDerived) + if err != nil { + return err + } + su.onNewLocalSafeData() + return nil +} + +func (su *SupervisorBackend) UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.BlockRef) error { + return su.chainDBs.UpdateFinalizedL1(finalized) +} + +// Access to synchronous processing for tests +// ---------------------------- + +func (su *SupervisorBackend) SyncEvents(chainID types.ChainID) error { + ch, ok := su.chainProcessors.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + ch.ProcessToHead() + return nil +} + +func (su *SupervisorBackend) SyncCrossUnsafe(chainID types.ChainID) error { + ch, ok := su.crossUnsafeProcessors.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + return ch.ProcessWork() +} + +func (su *SupervisorBackend) SyncCrossSafe(chainID types.ChainID) error { + ch, ok := su.crossSafeProcessors.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + return ch.ProcessWork() } diff --git a/op-supervisor/supervisor/backend/backend_test.go b/op-supervisor/supervisor/backend/backend_test.go new file mode 100644 index 0000000000000..c104bf0bae5dc --- /dev/null +++ b/op-supervisor/supervisor/backend/backend_test.go @@ -0,0 +1,141 @@ +package backend + +import ( + "context" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + types2 "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils" + "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/ethereum-optimism/optimism/op-supervisor/metrics" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func TestBackendLifetime(t *testing.T) { + logger := testlog.Logger(t, log.LvlInfo) + m := metrics.NoopMetrics + dataDir := t.TempDir() + chainA := types.ChainIDFromUInt64(900) + chainB := types.ChainIDFromUInt64(901) + depSet, err := depset.NewStaticConfigDependencySet( + map[types.ChainID]*depset.StaticConfigDependency{ + chainA: { + ChainIndex: 900, + ActivationTime: 42, + HistoryMinTime: 100, + }, + chainB: { + ChainIndex: 901, + ActivationTime: 30, + HistoryMinTime: 20, + }, + }) + require.NoError(t, err) + cfg := &config.Config{ + Version: "test", + LogConfig: oplog.CLIConfig{}, + MetricsConfig: opmetrics.CLIConfig{}, + PprofConfig: oppprof.CLIConfig{}, + RPC: oprpc.CLIConfig{}, + DependencySetSource: depSet, + SynchronousProcessors: true, + MockRun: false, + L2RPCs: nil, + Datadir: dataDir, + } + + b, err := NewSupervisorBackend(context.Background(), logger, m, cfg) + require.NoError(t, err) + t.Log("initialized!") + + src := &testutils.MockL1Source{} + + blockX := eth.BlockRef{ + Hash: common.Hash{0xaa}, + Number: 0, + ParentHash: common.Hash{}, // genesis has no parent hash + Time: 10000, + } + blockY := eth.BlockRef{ + Hash: common.Hash{0xbb}, + Number: blockX.Number + 1, + ParentHash: blockX.Hash, + Time: blockX.Time + 2, + } + + require.NoError(t, b.AttachProcessorSource(chainA, src)) + + require.FileExists(t, filepath.Join(cfg.Datadir, "900", "log.db"), "must have logs DB 900") + require.FileExists(t, filepath.Join(cfg.Datadir, "901", "log.db"), "must have logs DB 901") + require.FileExists(t, filepath.Join(cfg.Datadir, "900", "local_safe.db"), "must have local safe DB 900") + require.FileExists(t, filepath.Join(cfg.Datadir, "901", "local_safe.db"), "must have local safe DB 901") + require.FileExists(t, filepath.Join(cfg.Datadir, "900", "cross_safe.db"), "must have cross safe DB 900") + require.FileExists(t, filepath.Join(cfg.Datadir, "901", "cross_safe.db"), "must have cross safe DB 901") + + err = b.Start(context.Background()) + require.NoError(t, err) + t.Log("started!") + + _, err = b.UnsafeView(context.Background(), chainA, types.ReferenceView{}) + require.ErrorIs(t, err, types.ErrFuture, "no data yet, need local-unsafe") + + src.ExpectL1BlockRefByNumber(0, blockX, nil) + src.ExpectFetchReceipts(blockX.Hash, &testutils.MockBlockInfo{ + InfoHash: blockX.Hash, + InfoParentHash: blockX.ParentHash, + InfoNum: blockX.Number, + InfoTime: blockX.Time, + InfoReceiptRoot: types2.EmptyReceiptsHash, + }, nil, nil) + + src.ExpectL1BlockRefByNumber(1, blockY, nil) + src.ExpectFetchReceipts(blockY.Hash, &testutils.MockBlockInfo{ + InfoHash: blockY.Hash, + InfoParentHash: blockY.ParentHash, + InfoNum: blockY.Number, + InfoTime: blockY.Time, + InfoReceiptRoot: types2.EmptyReceiptsHash, + }, nil, nil) + + src.ExpectL1BlockRefByNumber(2, eth.L1BlockRef{}, ethereum.NotFound) + + err = b.UpdateLocalUnsafe(context.Background(), chainA, blockY) + require.NoError(t, err) + // Make the processing happen, so we can rely on the new chain information, + // and not run into errors for future data that isn't mocked at this time. + proc, _ := b.chainProcessors.Get(chainA) + proc.ProcessToHead() + + _, err = b.UnsafeView(context.Background(), chainA, types.ReferenceView{}) + require.ErrorIs(t, err, types.ErrFuture, "still no data yet, need cross-unsafe") + + err = b.chainDBs.UpdateCrossUnsafe(chainA, types.BlockSeal{ + Hash: blockX.Hash, + Number: blockX.Number, + Timestamp: blockX.Time, + }) + require.NoError(t, err) + + v, err := b.UnsafeView(context.Background(), chainA, types.ReferenceView{}) + require.NoError(t, err, "have a functioning cross/local unsafe view now") + require.Equal(t, blockX.ID(), v.Cross) + require.Equal(t, blockY.ID(), v.Local) + + err = b.Stop(context.Background()) + require.NoError(t, err) + t.Log("stopped!") +} diff --git a/op-supervisor/supervisor/backend/chain_metrics.go b/op-supervisor/supervisor/backend/chain_metrics.go index e51dbabbd7a2f..a067ddc3bfc58 100644 --- a/op-supervisor/supervisor/backend/chain_metrics.go +++ b/op-supervisor/supervisor/backend/chain_metrics.go @@ -10,7 +10,7 @@ type Metrics interface { CacheAdd(chainID types.ChainID, label string, cacheSize int, evicted bool) CacheGet(chainID types.ChainID, label string, hit bool) - RecordDBEntryCount(chainID types.ChainID, count int64) + RecordDBEntryCount(chainID types.ChainID, kind string, count int64) RecordDBSearchEntriesRead(chainID types.ChainID, count int64) } @@ -36,8 +36,8 @@ func (c *chainMetrics) CacheGet(label string, hit bool) { c.delegate.CacheGet(c.chainID, label, hit) } -func (c *chainMetrics) RecordDBEntryCount(count int64) { - c.delegate.RecordDBEntryCount(c.chainID, count) +func (c *chainMetrics) RecordDBEntryCount(kind string, count int64) { + c.delegate.RecordDBEntryCount(c.chainID, kind, count) } func (c *chainMetrics) RecordDBSearchEntriesRead(count int64) { diff --git a/op-supervisor/supervisor/backend/cross/cycle.go b/op-supervisor/supervisor/backend/cross/cycle.go new file mode 100644 index 0000000000000..9884856a34a50 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/cycle.go @@ -0,0 +1,308 @@ +package cross + +import ( + "errors" + "fmt" + "strings" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +var ( + ErrCycle = errors.New("cycle detected") + ErrExecMsgHasInvalidIndex = errors.New("executing message has invalid log index") + ErrExecMsgUnknownChain = errors.New("executing message references unknown chain") +) + +// CycleCheckDeps is an interface for checking cyclical dependencies between logs. +type CycleCheckDeps interface { + // OpenBlock returns log data for the requested block, to be used for cycle checking. + OpenBlock(chainID types.ChainID, blockNum uint64) (block eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) +} + +// node represents a log entry in the dependency graph. +// It could be an initiating message, executing message, both, or neither. +// It is uniquely identified by chain index and the log index within its parent block. +type node struct { + chainIndex types.ChainIndex + logIndex uint32 +} + +// graph is a directed graph of message dependencies. +// It is represented as an adjacency list with in-degree counts to be friendly to cycle checking. +type graph struct { + inDegree0 map[node]struct{} + inDegreeNon0 map[node]uint32 + outgoingEdges map[node][]node +} + +// addEdge adds a directed edge from -> to in the graph. +func (g *graph) addEdge(from, to node) { + // Remove the target from inDegree0 if it's there + delete(g.inDegree0, to) + + // Add or increment the target's in-degree count + g.inDegreeNon0[to] += 1 + + // Add the outgoing edge + g.outgoingEdges[from] = append(g.outgoingEdges[from], to) +} + +// HazardCycleChecks checks for cyclical dependencies between logs at the given timestamp. +// Here the timestamp invariant alone does not ensure ordering of messages. +// +// We perform this check in 3 steps: +// - Gather all logs across all hazard blocks at the given timestamp. +// - Build the logs into a directed graph of dependencies between logs. +// - Check the graph for cycles. +// +// The edges of the graph are determined by: +// - For all logs except the first in a block, there is an edge from the previous log. +// - For all executing messages, there is an edge from the initiating message. +// +// The edges between sequential logs ensure the graph is well-connected and free of any +// disjoint subgraphs that would make cycle checking more difficult. +// +// The cycle check is performed by executing Kahn's topological sort algorithm which +// succeeds if and only if a graph is acyclic. +// +// Returns nil if no cycles are found or ErrCycle if a cycle is detected. +func HazardCycleChecks(d CycleCheckDeps, inTimestamp uint64, hazards map[types.ChainIndex]types.BlockSeal) error { + g, err := buildGraph(d, inTimestamp, hazards) + if err != nil { + return err + } + + return checkGraph(g) +} + +// gatherLogs collects all log counts and executing messages across all hazard blocks. +// Returns: +// - map of chain index to its log count +// - map of chain index to map of log index to executing message (nil if doesn't exist or ignored) +func gatherLogs(d CycleCheckDeps, inTimestamp uint64, hazards map[types.ChainIndex]types.BlockSeal) ( + map[types.ChainIndex]uint32, + map[types.ChainIndex]map[uint32]*types.ExecutingMessage, + error, +) { + logCounts := make(map[types.ChainIndex]uint32) + execMsgs := make(map[types.ChainIndex]map[uint32]*types.ExecutingMessage) + + for hazardChainIndex, hazardBlock := range hazards { + // TODO(#11105): translate chain index to chain ID + hazardChainID := types.ChainIDFromUInt64(uint64(hazardChainIndex)) + bl, logCount, msgs, err := d.OpenBlock(hazardChainID, hazardBlock.Number) + if err != nil { + return nil, nil, fmt.Errorf("failed to open block: %w", err) + } + + if !blockSealMatchesRef(hazardBlock, bl) { + return nil, nil, fmt.Errorf("tried to open block %s of chain %s, but got different block %s than expected, use a reorg lock for consistency", hazardBlock, hazardChainID, bl) + } + + // Validate executing message indices + for logIdx := range msgs { + if logIdx >= logCount { + return nil, nil, fmt.Errorf("%w: log index %d >= log count %d", ErrExecMsgHasInvalidIndex, logIdx, logCount) + } + } + + // Store log count and in-timestamp executing messages + logCounts[hazardChainIndex] = logCount + + if len(msgs) > 0 { + if _, exists := execMsgs[hazardChainIndex]; !exists { + execMsgs[hazardChainIndex] = make(map[uint32]*types.ExecutingMessage) + } + } + for logIdx, msg := range msgs { + if msg.Timestamp == inTimestamp { + execMsgs[hazardChainIndex][logIdx] = msg + } + } + } + + return logCounts, execMsgs, nil +} + +// buildGraph constructs a dependency graph from the hazard blocks. +func buildGraph(d CycleCheckDeps, inTimestamp uint64, hazards map[types.ChainIndex]types.BlockSeal) (*graph, error) { + g := &graph{ + inDegree0: make(map[node]struct{}), + inDegreeNon0: make(map[node]uint32), + outgoingEdges: make(map[node][]node), + } + + logCounts, execMsgs, err := gatherLogs(d, inTimestamp, hazards) + if err != nil { + return nil, err + } + + // Add nodes for each log in the block, and add edges between sequential logs + for hazardChainIndex, logCount := range logCounts { + for i := uint32(0); i < logCount; i++ { + k := node{ + chainIndex: hazardChainIndex, + logIndex: i, + } + + if i == 0 { + // First log in block has no dependencies + g.inDegree0[k] = struct{}{} + } else { + // Add edge: prev log <> current log + prevKey := node{ + chainIndex: hazardChainIndex, + logIndex: i - 1, + } + g.addEdge(prevKey, k) + } + } + } + + // Add edges for executing messages to their initiating messages + for hazardChainIndex, msgs := range execMsgs { + for execLogIdx, m := range msgs { + // Error if the chain is unknown + if _, ok := hazards[m.Chain]; !ok { + return nil, ErrExecMsgUnknownChain + } + + // Check if we care about the init message + initChainMsgs, ok := execMsgs[m.Chain] + if !ok { + continue + } + if _, ok := initChainMsgs[m.LogIdx]; !ok { + continue + } + + // Check if the init message exists + if logCount, ok := logCounts[m.Chain]; !ok || m.LogIdx >= logCount { + return nil, fmt.Errorf("%w: initiating message log index out of bounds", types.ErrConflict) + } + + initKey := node{ + chainIndex: m.Chain, + logIndex: m.LogIdx, + } + execKey := node{ + chainIndex: hazardChainIndex, + logIndex: execLogIdx, + } + + // Disallow self-referencing messages + // This should not be possible since the executing message contains the hash of the initiating message. + if initKey == execKey { + return nil, fmt.Errorf("%w: self referencial message", types.ErrConflict) + } + + // Add the edge + g.addEdge(initKey, execKey) + } + } + + return g, nil +} + +// checkGraph uses Kahn's topological sort algorithm to check for cycles in the graph. +// +// Returns: +// - nil for acyclic graphs. +// - ErrCycle for cyclic graphs. +// +// Algorithm: +// 1. for each node with in-degree 0 (i.e. no dependencies), add it to the result, remove it from the work. +// 2. along with removing, remove the outgoing edges +// 3. if there is no node left with in-degree 0, then there is a cycle +func checkGraph(g *graph) error { + for { + // Process all nodes that have no incoming edges + for k := range g.inDegree0 { + // Remove all outgoing edges from this node + for _, out := range g.outgoingEdges[k] { + g.inDegreeNon0[out] -= 1 + if g.inDegreeNon0[out] == 0 { + delete(g.inDegreeNon0, out) + g.inDegree0[out] = struct{}{} + } + } + delete(g.outgoingEdges, k) + delete(g.inDegree0, k) + } + + // If there are new nodes with in-degree 0 then process them + if len(g.inDegree0) > 0 { + continue + } + + // We're done processing so check for remaining nodes + if len(g.inDegreeNon0) == 0 { + // Done, without cycles! + return nil + } + + // Some nodes left; there must be a cycle. + return ErrCycle + } +} + +func blockSealMatchesRef(seal types.BlockSeal, ref eth.BlockRef) bool { + return seal.Number == ref.Number && seal.Hash == ref.Hash +} + +// GenerateMermaidDiagram creates a Mermaid flowchart diagram from the graph data for debugging. +func GenerateMermaidDiagram(g *graph) string { + var sb strings.Builder + + sb.WriteString("flowchart TD\n") + + // Helper function to get a unique ID for each node + getNodeID := func(k node) string { + return fmt.Sprintf("N%d_%d", k.chainIndex, k.logIndex) + } + + // Helper function to get a label for each node + getNodeLabel := func(k node) string { + return fmt.Sprintf("C%d:L%d", k.chainIndex, k.logIndex) + } + + // Function to add a node to the diagram + addNode := func(k node, inDegree uint32) { + nodeID := getNodeID(k) + nodeLabel := getNodeLabel(k) + var shape string + if inDegree == 0 { + shape = "((%s))" + } else { + shape = "[%s]" + } + sb.WriteString(fmt.Sprintf(" %s"+shape+"\n", nodeID, nodeLabel)) + } + + // Add all nodes + for k := range g.inDegree0 { + addNode(k, 0) + } + for k, inDegree := range g.inDegreeNon0 { + addNode(k, inDegree) + } + + // Add all edges + for from, tos := range g.outgoingEdges { + fromID := getNodeID(from) + for _, to := range tos { + toID := getNodeID(to) + sb.WriteString(fmt.Sprintf(" %s --> %s\n", fromID, toID)) + } + } + + // Add a legend + sb.WriteString(" subgraph Legend\n") + sb.WriteString(" L1((In-Degree 0))\n") + sb.WriteString(" L2[In-Degree > 0]\n") + sb.WriteString(" end\n") + + return sb.String() +} diff --git a/op-supervisor/supervisor/backend/cross/cycle_test.go b/op-supervisor/supervisor/backend/cross/cycle_test.go new file mode 100644 index 0000000000000..e160023caf818 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/cycle_test.go @@ -0,0 +1,557 @@ +package cross + +import ( + "errors" + "fmt" + "strconv" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type mockCycleCheckDeps struct { + openBlockFn func(chainID types.ChainID, blockNum uint64) (eth.BlockRef, uint32, map[uint32]*types.ExecutingMessage, error) +} + +func (m *mockCycleCheckDeps) OpenBlock(chainID types.ChainID, blockNum uint64) (eth.BlockRef, uint32, map[uint32]*types.ExecutingMessage, error) { + return m.openBlockFn(chainID, blockNum) +} + +type chainBlockDef struct { + logCount uint32 + messages map[uint32]*types.ExecutingMessage + error error +} + +type hazardCycleChecksTestCase struct { + name string + chainBlocks map[string]chainBlockDef + expectErr error + msg string + + // Optional overrides + hazards map[types.ChainIndex]types.BlockSeal + openBlockFn func(chainID types.ChainID, blockNum uint64) (eth.BlockRef, uint32, map[uint32]*types.ExecutingMessage, error) +} + +func runHazardCycleChecksTestCaseGroup(t *testing.T, group string, tests []hazardCycleChecksTestCase) { + for _, tc := range tests { + t.Run(group+"/"+tc.name, func(t *testing.T) { + runHazardCycleChecksTestCase(t, tc) + }) + } +} + +func runHazardCycleChecksTestCase(t *testing.T, tc hazardCycleChecksTestCase) { + // Create mocked dependencies + deps := &mockCycleCheckDeps{ + openBlockFn: func(chainID types.ChainID, blockNum uint64) (eth.BlockRef, uint32, map[uint32]*types.ExecutingMessage, error) { + // Use override if provided + if tc.openBlockFn != nil { + return tc.openBlockFn(chainID, blockNum) + } + + // Default behavior + chainStr := chainID.String() + def, ok := tc.chainBlocks[chainStr] + if !ok { + return eth.BlockRef{}, 0, nil, errors.New("unexpected chain") + } + if def.error != nil { + return eth.BlockRef{}, 0, nil, def.error + } + return eth.BlockRef{Number: blockNum}, def.logCount, def.messages, nil + }, + } + + // Generate hazards map automatically if not explicitly provided + var hazards map[types.ChainIndex]types.BlockSeal + if tc.hazards != nil { + hazards = tc.hazards + } else { + hazards = make(map[types.ChainIndex]types.BlockSeal) + for chainStr := range tc.chainBlocks { + hazards[chainIndex(chainStr)] = types.BlockSeal{Number: 1} + } + } + + // Run the test + err := HazardCycleChecks(deps, 100, hazards) + + // No error expected + if tc.expectErr == nil { + require.NoError(t, err, tc.msg) + return + } + + // Error expected, make sure it's the right one + require.Error(t, err, tc.msg) + if errors.Is(err, tc.expectErr) { + require.ErrorIs(t, err, tc.expectErr, tc.msg) + } else { + require.Contains(t, err.Error(), tc.expectErr.Error(), tc.msg) + } +} + +func chainIndex(s string) types.ChainIndex { + id, err := strconv.ParseUint(s, 10, 32) + if err != nil { + panic(fmt.Sprintf("invalid chain index in test: %v", err)) + } + return types.ChainIndex(id) +} + +func execMsg(chain string, logIdx uint32) *types.ExecutingMessage { + return execMsgWithTimestamp(chain, logIdx, 100) +} + +func execMsgWithTimestamp(chain string, logIdx uint32, timestamp uint64) *types.ExecutingMessage { + return &types.ExecutingMessage{ + Chain: chainIndex(chain), + LogIdx: logIdx, + Timestamp: timestamp, + } +} + +var emptyChainBlocks = map[string]chainBlockDef{ + "1": { + logCount: 0, + messages: map[uint32]*types.ExecutingMessage{}, + }, +} + +func TestHazardCycleChecksFailures(t *testing.T) { + testOpenBlockErr := errors.New("test OpenBlock error") + tests := []hazardCycleChecksTestCase{ + { + name: "empty hazards", + chainBlocks: emptyChainBlocks, + hazards: make(map[types.ChainIndex]types.BlockSeal), + expectErr: nil, + msg: "expected no error when there are no hazards", + }, + { + name: "nil hazards", + chainBlocks: emptyChainBlocks, + hazards: nil, + expectErr: nil, + msg: "expected no error when there are nil hazards", + }, + { + name: "nil blocks", + chainBlocks: nil, + hazards: nil, + expectErr: nil, + msg: "expected no error when there are nil blocks and hazards", + }, + { + name: "failed to open block error", + chainBlocks: emptyChainBlocks, + openBlockFn: func(chainID types.ChainID, blockNum uint64) (eth.BlockRef, uint32, map[uint32]*types.ExecutingMessage, error) { + return eth.BlockRef{}, 0, nil, testOpenBlockErr + }, + expectErr: errors.New("failed to open block"), + msg: "expected error when OpenBlock fails", + }, + { + name: "block mismatch error", + chainBlocks: emptyChainBlocks, + // openBlockFn returns a block number that doesn't match the expected block number. + openBlockFn: func(chainID types.ChainID, blockNum uint64) (eth.BlockRef, uint32, map[uint32]*types.ExecutingMessage, error) { + return eth.BlockRef{Number: blockNum + 1}, 0, make(map[uint32]*types.ExecutingMessage), nil + }, + expectErr: errors.New("tried to open block"), + msg: "expected error due to block mismatch", + }, + { + name: "invalid log index error", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 3, + messages: map[uint32]*types.ExecutingMessage{ + 5: execMsg("1", 0), // Invalid index >= logCount. + }, + }, + }, + expectErr: ErrExecMsgHasInvalidIndex, + msg: "expected invalid log index error", + }, + { + name: "self reference detected error", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 1, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("1", 0), // Points at itself. + }, + }, + }, + expectErr: types.ErrConflict, + msg: "expected self reference detection error", + }, + { + name: "unknown chain", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("2", 0), // References chain 2 which isn't in hazards. + }, + }, + }, + hazards: map[types.ChainIndex]types.BlockSeal{ + 1: {Number: 1}, // Only include chain 1. + }, + expectErr: ErrExecMsgUnknownChain, + msg: "expected unknown chain error", + }, + } + runHazardCycleChecksTestCaseGroup(t, "Failure", tests) +} + +func TestHazardCycleChecksNoCycle(t *testing.T) { + tests := []hazardCycleChecksTestCase{ + { + name: "no logs", + chainBlocks: emptyChainBlocks, + expectErr: nil, + msg: "expected no cycle found for block with no logs", + }, + { + name: "one basic log", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 1, + messages: map[uint32]*types.ExecutingMessage{}, + }, + }, + msg: "expected no cycle found for single basic log", + }, + { + name: "one exec log", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 0), + }, + }, + }, + msg: "expected no cycle found for single exec log", + }, + { + name: "two basic logs", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{}, + }, + }, + msg: "expected no cycle found for two basic logs", + }, + { + name: "two exec logs to same target", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 3, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 0), + 2: execMsg("1", 0), + }, + }, + }, + msg: "expected no cycle found for two exec logs pointing at the same log", + }, + { + name: "two exec logs to different targets", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 3, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 0), + 2: execMsg("1", 1), + }, + }, + }, + msg: "expected no cycle found for two exec logs pointing at the different logs", + }, + { + name: "one basic log one exec log", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 0), + }, + }, + }, + msg: "expected no cycle found for one basic and one exec log", + }, + { + name: "first log is exec", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 1, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("2", 0), + }, + }, + "2": { + logCount: 1, + messages: nil, + }, + }, + msg: "expected no cycle found first log is exec", + }, + { + name: "cycle through older timestamp", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("2", 0), + 1: execMsgWithTimestamp("2", 1, 101), + }, + }, + "2": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("1", 1), + }, + }, + }, + msg: "expected no cycle detection error for cycle through messages with different timestamps", + }, + // This should be caught by earlier validations, but included for completeness. + { + name: "cycle through younger timestamp", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("2", 0), + 1: execMsgWithTimestamp("2", 1, 99), + }, + }, + "2": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("1", 1), + }, + }, + }, + msg: "expected no cycle detection error for cycle through messages with different timestamps", + }, + } + runHazardCycleChecksTestCaseGroup(t, "NoCycle", tests) +} + +func TestHazardCycleChecksCycle(t *testing.T) { + tests := []hazardCycleChecksTestCase{ + { + name: "2-cycle in single chain with first log", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 3, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("1", 2), + 2: execMsg("1", 0), + }, + }, + }, + expectErr: ErrCycle, + msg: "expected cycle detection error", + }, + { + name: "2-cycle in single chain with first log, adjacent", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("1", 1), + 1: execMsg("1", 0), + }, + }, + }, + expectErr: ErrCycle, + msg: "expected cycle detection error", + }, + { + name: "2-cycle in single chain, not first, adjacent", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 3, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 2), + 2: execMsg("1", 1), + }, + }, + }, + expectErr: ErrCycle, + msg: "expected cycle detection error", + }, + { + name: "2-cycle in single chain, not first, not adjacent", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 4, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 3), + 3: execMsg("1", 1), + }, + }, + }, + expectErr: ErrCycle, + msg: "expected cycle detection error", + }, + { + name: "2-cycle across chains", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("2", 0), + }, + }, + "2": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("1", 1), + }, + }, + }, + expectErr: ErrCycle, + msg: "expected cycle detection error for cycle through executing messages", + }, + { + name: "3-cycle in single chain", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 4, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 2), // Points to log 2 + 2: execMsg("1", 3), // Points to log 3 + 3: execMsg("1", 1), // Points back to log 1 + }, + }, + }, + expectErr: ErrCycle, + msg: "expected cycle detection error for 3-node cycle", + }, + { + name: "cycle through adjacency dependency", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 10, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 5), // Points to log 5 + 5: execMsg("1", 2), // Points back to log 2 which is adjacent to log 1 + }, + }, + }, + expectErr: ErrCycle, + msg: "expected cycle detection error for when cycle goes through adjacency dependency", + }, + { + name: "2-cycle across chains with 3 hazard chains", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("2", 1), + }, + }, + "2": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 1: execMsg("1", 1), + }, + }, + "3": {}, + }, + expectErr: ErrCycle, + hazards: map[types.ChainIndex]types.BlockSeal{ + 1: {Number: 1}, + 2: {Number: 1}, + 3: {Number: 1}, + }, + msg: "expected cycle detection error for cycle through executing messages", + }, + } + runHazardCycleChecksTestCaseGroup(t, "Cycle", tests) +} + +const ( + largeGraphChains = 10 + largeGraphLogsPerChain = 10000 +) + +func TestHazardCycleChecksLargeGraphNoCycle(t *testing.T) { + // Create a large but acyclic graph + chainBlocks := make(map[string]chainBlockDef) + for i := 1; i <= largeGraphChains; i++ { + msgs := make(map[uint32]*types.ExecutingMessage) + // Create a chain of dependencies across chains + if i > 1 { + for j := uint32(0); j < largeGraphLogsPerChain; j++ { + // Point to previous chain, same log index + msgs[j] = execMsg(strconv.Itoa(i-1), j) + } + } + chainBlocks[strconv.Itoa(i)] = chainBlockDef{ + logCount: largeGraphLogsPerChain, + messages: msgs, + } + } + + tc := hazardCycleChecksTestCase{ + name: "Large graph without cycles", + chainBlocks: chainBlocks, + expectErr: nil, + msg: "expected no cycle in large acyclic graph", + } + runHazardCycleChecksTestCase(t, tc) +} + +func TestHazardCycleChecksLargeGraphCycle(t *testing.T) { + // Create a large graph with a cycle hidden in it + const cycleChain = 3 + const cycleLogIndex = 5678 + + chainBlocks := make(map[string]chainBlockDef) + for i := 1; i <= largeGraphChains; i++ { + msgs := make(map[uint32]*types.ExecutingMessage) + + // Create a chain of dependencies across chains + if i > 1 { + for j := uint32(0); j < largeGraphLogsPerChain; j++ { + if i == cycleChain && j == cycleLogIndex { + // Create a cycle by pointing back to chain 1 + msgs[j] = execMsg("1", cycleLogIndex+1) + } else { + // Normal case: point to previous chain, same log index + msgs[j] = execMsg(strconv.Itoa(i-1), j) + } + } + } else { + // In chain 1, create the other side of the cycle + msgs[cycleLogIndex+1] = execMsg(strconv.Itoa(cycleChain), cycleLogIndex) + } + + chainBlocks[strconv.Itoa(i)] = chainBlockDef{ + logCount: largeGraphLogsPerChain, + messages: msgs, + } + } + + tc := hazardCycleChecksTestCase{ + name: "Large graph with cycle", + chainBlocks: chainBlocks, + expectErr: ErrCycle, + msg: "expected to detect cycle in large cyclic graph", + } + runHazardCycleChecksTestCase(t, tc) +} diff --git a/op-supervisor/supervisor/backend/cross/safe_frontier.go b/op-supervisor/supervisor/backend/cross/safe_frontier.go new file mode 100644 index 0000000000000..31b48843fd9a6 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/safe_frontier.go @@ -0,0 +1,59 @@ +package cross + +import ( + "errors" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type SafeFrontierCheckDeps interface { + CandidateCrossSafe(chain types.ChainID) (derivedFromScope, crossSafe eth.BlockRef, err error) + + CrossDerivedFrom(chainID types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) + + DependencySet() depset.DependencySet +} + +// HazardSafeFrontierChecks verifies all the hazard blocks are either: +// - already cross-safe. +// - the first (if not first: local blocks to verify before proceeding) +// local-safe block, after the cross-safe block. +func HazardSafeFrontierChecks(d SafeFrontierCheckDeps, inL1DerivedFrom eth.BlockID, hazards map[types.ChainIndex]types.BlockSeal) error { + depSet := d.DependencySet() + for hazardChainIndex, hazardBlock := range hazards { + hazardChainID, err := depSet.ChainIDFromIndex(hazardChainIndex) + if err != nil { + if errors.Is(err, types.ErrUnknownChain) { + err = fmt.Errorf("cannot cross-safe verify block %s of unknown chain index %s: %w", hazardBlock, hazardChainIndex, types.ErrConflict) + } + return err + } + initDerivedFrom, err := d.CrossDerivedFrom(hazardChainID, hazardBlock.ID()) + if err != nil { + if errors.Is(err, types.ErrFuture) { + // If not in cross-safe scope, then check if it's the candidate cross-safe block. + initDerivedFrom, initSelf, err := d.CandidateCrossSafe(hazardChainID) + if err != nil { + return fmt.Errorf("failed to determine cross-safe candidate block of hazard dependency %s (chain %s): %w", hazardBlock, hazardChainID, err) + } + if initSelf.Number == hazardBlock.Number && initSelf.ID() != hazardBlock.ID() { + return fmt.Errorf("expected block %s (chain %d) does not match candidate local-safe block %s: %w", + hazardBlock, hazardChainID, initSelf, types.ErrConflict) + } + if initDerivedFrom.Number > inL1DerivedFrom.Number { + return fmt.Errorf("local-safe hazard block %s derived from L1 block %s is after scope %s: %w", + hazardBlock.ID(), initDerivedFrom, inL1DerivedFrom, types.ErrOutOfScope) + } + } else { + return fmt.Errorf("failed to determine cross-derived of hazard block %s (chain %s): %w", hazardBlock, hazardChainID, err) + } + } else if initDerivedFrom.Number > inL1DerivedFrom.Number { + return fmt.Errorf("cross-safe hazard block %s derived from L1 block %s is after scope %s: %w", + hazardBlock.ID(), initDerivedFrom, inL1DerivedFrom, types.ErrOutOfScope) + } + } + return nil +} diff --git a/op-supervisor/supervisor/backend/cross/safe_frontier_test.go b/op-supervisor/supervisor/backend/cross/safe_frontier_test.go new file mode 100644 index 0000000000000..6848acf841cd8 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/safe_frontier_test.go @@ -0,0 +1,201 @@ +package cross + +import ( + "errors" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestHazardSafeFrontierChecks(t *testing.T) { + t.Run("empty hazards", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{} + l1DerivedFrom := eth.BlockID{} + hazards := map[types.ChainIndex]types.BlockSeal{} + // when there are no hazards, + // no work is done, and no error is returned + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.NoError(t, err) + }) + t.Run("unknown chain", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{ + deps: mockDependencySet{ + chainIDFromIndexfn: func() (types.ChainID, error) { + return types.ChainID{}, types.ErrUnknownChain + }, + }, + } + l1DerivedFrom := eth.BlockID{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + // when there is one hazard, and ChainIDFromIndex returns ErrUnknownChain, + // an error is returned as a ErrConflict + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.ErrorIs(t, err, types.ErrConflict) + }) + t.Run("initDerivedFrom in scope", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{} + sfcd.crossDerivedFromFn = func() (types.BlockSeal, error) { + return types.BlockSeal{Number: 1}, nil + } + l1DerivedFrom := eth.BlockID{Number: 2} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + // when there is one hazard, and CrossDerivedFrom returns a BlockSeal within scope + // (ie the hazard's block number is less than or equal to the derivedFrom block number), + // no error is returned + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.NoError(t, err) + }) + t.Run("initDerivedFrom out of scope", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{} + sfcd.crossDerivedFromFn = func() (types.BlockSeal, error) { + return types.BlockSeal{Number: 3}, nil + } + l1DerivedFrom := eth.BlockID{Number: 2} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + // when there is one hazard, and CrossDerivedFrom returns a BlockSeal out of scope + // (ie the hazard's block number is greater than the derivedFrom block number), + // an error is returned as a ErrOutOfScope + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.ErrorIs(t, err, types.ErrOutOfScope) + }) + t.Run("errFuture: candidate cross safe failure", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{} + sfcd.crossDerivedFromFn = func() (types.BlockSeal, error) { + return types.BlockSeal{Number: 3}, types.ErrFuture + } + sfcd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{}, + eth.BlockRef{Number: 3, Hash: common.BytesToHash([]byte{0x01})}, + errors.New("some error") + } + l1DerivedFrom := eth.BlockID{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + // when there is one hazard, and CrossDerivedFrom returns an ErrFuture, + // and CandidateCrossSafe returns an error, + // the error from CandidateCrossSafe is returned + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.ErrorContains(t, err, "some error") + }) + t.Run("errFuture: expected block does not match candidate", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{} + sfcd.crossDerivedFromFn = func() (types.BlockSeal, error) { + return types.BlockSeal{}, types.ErrFuture + } + sfcd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{}, + eth.BlockRef{Number: 3, Hash: common.BytesToHash([]byte{0x01})}, + nil + } + l1DerivedFrom := eth.BlockID{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3, Hash: common.BytesToHash([]byte{0x02})}} + // when there is one hazard, and CrossDerivedFrom returns an ErrFuture, + // and CandidateCrossSafe returns a candidate that does not match the hazard, + // (ie the candidate's block number is the same as the hazard's block number, but the hashes are different), + // an error is returned as a ErrConflict + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.ErrorIs(t, err, types.ErrConflict) + }) + t.Run("errFuture: local-safe hazard out of scope", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{} + sfcd.crossDerivedFromFn = func() (types.BlockSeal, error) { + return types.BlockSeal{}, types.ErrFuture + } + sfcd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{Number: 9}, + eth.BlockRef{}, + nil + } + l1DerivedFrom := eth.BlockID{Number: 8} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3, Hash: common.BytesToHash([]byte{0x02})}} + // when there is one hazard, and CrossDerivedFrom returns an ErrFuture, + // and the initDerivedFrom is out of scope, + // an error is returned as a ErrOutOfScope + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.ErrorIs(t, err, types.ErrOutOfScope) + }) + t.Run("CrossDerivedFrom Error", func(t *testing.T) { + sfcd := &mockSafeFrontierCheckDeps{} + sfcd.crossDerivedFromFn = func() (types.BlockSeal, error) { + return types.BlockSeal{}, errors.New("some error") + } + sfcd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{Number: 9}, + eth.BlockRef{}, + nil + } + l1DerivedFrom := eth.BlockID{Number: 8} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3, Hash: common.BytesToHash([]byte{0x02})}} + // when there is one hazard, and CrossDerivedFrom returns an ErrFuture, + // and the initDerivedFrom is out of scope, + // an error is returned as a ErrOutOfScope + err := HazardSafeFrontierChecks(sfcd, l1DerivedFrom, hazards) + require.ErrorContains(t, err, "some error") + }) +} + +type mockSafeFrontierCheckDeps struct { + deps mockDependencySet + candidateCrossSafeFn func() (derivedFromScope, crossSafe eth.BlockRef, err error) + crossDerivedFromFn func() (derivedFrom types.BlockSeal, err error) +} + +func (m *mockSafeFrontierCheckDeps) CandidateCrossSafe(chain types.ChainID) (derivedFromScope, crossSafe eth.BlockRef, err error) { + if m.candidateCrossSafeFn != nil { + return m.candidateCrossSafeFn() + } + return eth.BlockRef{}, eth.BlockRef{}, nil +} + +func (m *mockSafeFrontierCheckDeps) CrossDerivedFrom(chainID types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) { + if m.crossDerivedFromFn != nil { + return m.crossDerivedFromFn() + } + return types.BlockSeal{}, nil +} + +func (m *mockSafeFrontierCheckDeps) DependencySet() depset.DependencySet { + return m.deps +} + +type mockDependencySet struct { + chainIDFromIndexfn func() (types.ChainID, error) + canExecuteAtfn func() (bool, error) + canInitiateAtfn func() (bool, error) +} + +func (m mockDependencySet) CanExecuteAt(chain types.ChainID, timestamp uint64) (bool, error) { + if m.canExecuteAtfn != nil { + return m.canExecuteAtfn() + } + return true, nil +} + +func (m mockDependencySet) CanInitiateAt(chain types.ChainID, timestamp uint64) (bool, error) { + if m.canInitiateAtfn != nil { + return m.canInitiateAtfn() + } + return true, nil +} + +func (m mockDependencySet) ChainIDFromIndex(index types.ChainIndex) (types.ChainID, error) { + if m.chainIDFromIndexfn != nil { + return m.chainIDFromIndexfn() + } + return types.ChainID{}, nil +} + +func (m mockDependencySet) ChainIndexFromID(chain types.ChainID) (types.ChainIndex, error) { + return types.ChainIndex(0), nil +} + +func (m mockDependencySet) Chains() []types.ChainID { + return nil +} + +func (m mockDependencySet) HasChain(chain types.ChainID) bool { + return true +} diff --git a/op-supervisor/supervisor/backend/cross/safe_start.go b/op-supervisor/supervisor/backend/cross/safe_start.go new file mode 100644 index 0000000000000..acd97304ed148 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/safe_start.go @@ -0,0 +1,104 @@ +package cross + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type SafeStartDeps interface { + Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (includedIn types.BlockSeal, err error) + + CrossDerivedFrom(chainID types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) + + DependencySet() depset.DependencySet +} + +// CrossSafeHazards checks if the given messages all exist and pass invariants. +// It returns a hazard-set: if any intra-block messaging happened, +// these hazard blocks have to be verified. +func CrossSafeHazards(d SafeStartDeps, chainID types.ChainID, inL1DerivedFrom eth.BlockID, + candidate types.BlockSeal, execMsgs []*types.ExecutingMessage) (hazards map[types.ChainIndex]types.BlockSeal, err error) { + + hazards = make(map[types.ChainIndex]types.BlockSeal) + + // Warning for future: If we have sub-second distinct blocks (different block number), + // we need to increase precision on the above timestamp invariant. + // Otherwise a local block can depend on a future local block of the same chain, + // simply by pulling in a block of another chain, + // which then depends on a block of the original chain, + // all with the same timestamp, without message cycles. + + depSet := d.DependencySet() + + if len(execMsgs) > 0 { + if ok, err := depSet.CanExecuteAt(chainID, candidate.Timestamp); err != nil { + return nil, fmt.Errorf("cannot check message execution of block %s (chain %s): %w", candidate, chainID, err) + } else if !ok { + return nil, fmt.Errorf("cannot execute messages in block %s (chain %s): %w", candidate, chainID, types.ErrConflict) + } + } + + // check all executing messages + for _, msg := range execMsgs { + initChainID, err := depSet.ChainIDFromIndex(msg.Chain) + if err != nil { + if errors.Is(err, types.ErrUnknownChain) { + err = fmt.Errorf("msg %s may not execute from unknown chain %s: %w", msg, msg.Chain, types.ErrConflict) + } + return nil, err + } + if ok, err := depSet.CanInitiateAt(initChainID, msg.Timestamp); err != nil { + return nil, fmt.Errorf("cannot check message initiation of msg %s (chain %s): %w", msg, chainID, err) + } else if !ok { + return nil, fmt.Errorf("cannot allow initiating message %s (chain %s): %w", msg, chainID, types.ErrConflict) + } + if msg.Timestamp < candidate.Timestamp { + // If timestamp is older: invariant ensures non-cyclic ordering relative to other messages. + // Check that the block that they are included in is cross-safe already. + includedIn, err := d.Check(initChainID, msg.BlockNum, msg.LogIdx, msg.Hash) + if err != nil { + return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) + } + initDerivedFrom, err := d.CrossDerivedFrom(initChainID, includedIn.ID()) + if err != nil { + return nil, fmt.Errorf("msg %s included in non-cross-safe block %s: %w", msg, includedIn, err) + } + if initDerivedFrom.Number > inL1DerivedFrom.Number { + return nil, fmt.Errorf("msg %s was included in block %s derived from %s which is not in cross-safe scope %s: %w", + msg, includedIn, initDerivedFrom, inL1DerivedFrom, types.ErrOutOfScope) + } + } else if msg.Timestamp == candidate.Timestamp { + // If timestamp is equal: we have to inspect ordering of individual + // log events to ensure non-cyclic cross-chain message ordering. + // And since we may have back-and-forth messaging, we cannot wait till the initiating side is cross-safe. + // Thus check that it was included in a local-safe block, + // and then proceed with transitive block checks, + // to ensure the local block we depend on is becoming cross-safe also. + includedIn, err := d.Check(initChainID, msg.BlockNum, msg.LogIdx, msg.Hash) + if err != nil { + return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) + } + // As a hazard block, it will be checked to be included in a cross-safe block, + // or right after a cross-safe block in a local-safe block, in HazardSafeFrontierChecks. + if existing, ok := hazards[msg.Chain]; ok { + if existing != includedIn { + return nil, fmt.Errorf("found dependency on %s (chain %d), but already depend on %s", includedIn, initChainID, chainID) + } + } else { + // Mark it as hazard block + hazards[msg.Chain] = includedIn + } + } else { + // Timestamp invariant is broken: executing message tries to execute future block. + // The predeploy inbox contract should not have allowed this executing message through. + return nil, fmt.Errorf("executing message %s in %s breaks timestamp invariant", msg, candidate) + } + } + return hazards, nil +} diff --git a/op-supervisor/supervisor/backend/cross/safe_start_test.go b/op-supervisor/supervisor/backend/cross/safe_start_test.go new file mode 100644 index 0000000000000..1a5924e06963f --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/safe_start_test.go @@ -0,0 +1,341 @@ +package cross + +import ( + "errors" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestCrossSafeHazards(t *testing.T) { + t.Run("empty execMsgs", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{} + // when there are no execMsgs, + // no work is done, and no error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.NoError(t, err) + require.Empty(t, hazards) + }) + t.Run("CanExecuteAt returns false", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps = mockDependencySet{ + canExecuteAtfn: func() (bool, error) { + return false, nil + }, + } + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanExecuteAt returns false, + // no work is done and an error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.Empty(t, hazards) + }) + t.Run("CanExecuteAt returns error", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps = mockDependencySet{ + canExecuteAtfn: func() (bool, error) { + return false, errors.New("some error") + }, + } + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanExecuteAt returns false, + // no work is done and an error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("unknown chain", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps = mockDependencySet{ + chainIDFromIndexfn: func() (types.ChainID, error) { + return types.ChainID{}, types.ErrUnknownChain + }, + } + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and ChainIDFromIndex returns ErrUnknownChain, + // an error is returned as a ErrConflict + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.Empty(t, hazards) + }) + t.Run("ChainIDFromUInt64 returns error", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps = mockDependencySet{ + chainIDFromIndexfn: func() (types.ChainID, error) { + return types.ChainID{}, errors.New("some error") + }, + } + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and ChainIDFromIndex returns some other error, + // the error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("CanInitiateAt returns false", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps = mockDependencySet{ + canInitiateAtfn: func() (bool, error) { + return false, nil + }, + } + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanInitiateAt returns false, + // the error is returned as a ErrConflict + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.Empty(t, hazards) + }) + t.Run("CanInitiateAt returns error", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps = mockDependencySet{ + canInitiateAtfn: func() (bool, error) { + return false, errors.New("some error") + }, + } + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanInitiateAt returns an error, + // the error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is greater than candidate", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 10} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is greater than the candidate, + // an error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "breaks timestamp invariant") + require.Empty(t, hazards) + }) + t.Run("timestamp is equal, Check returns error", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + return types.BlockSeal{}, errors.New("some error") + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timetamp is equal to the candidate, + // and check returns an error, + // that error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is equal, same hazard twice", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + execMsgs := []*types.ExecutingMessage{em1, em2} + // when there are two execMsgs, and both are equal time to the candidate, + // and check returns the same includedIn for both + // they load the hazards once, and return no error + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.NoError(t, err) + require.Equal(t, hazards, map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): sampleBlockSeal}) + }) + t.Run("timestamp is equal, different hazards", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + // set the check function to return a different BlockSeal for the second call + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + sampleBlockSeal2 := types.BlockSeal{Number: 333, Hash: common.BytesToHash([]byte{0x22})} + calls := 0 + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + defer func() { calls++ }() + if calls == 0 { + return sampleBlockSeal, nil + } + return sampleBlockSeal2, nil + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + execMsgs := []*types.ExecutingMessage{em1, em2} + // when there are two execMsgs, and both are equal time to the candidate, + // and check returns different includedIn for the two, + // an error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "but already depend on") + require.Empty(t, hazards) + }) + t.Run("timestamp is less, check returns error", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + return types.BlockSeal{}, errors.New("some error") + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and check returns an error, + // that error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is less, CrossDerivedFrom returns error", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + ssd.derivedFromFn = func() (derivedFrom types.BlockSeal, err error) { + return types.BlockSeal{}, errors.New("some error") + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and CrossDerivedFrom returns aan error, + // that error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is less, CrossDerivedFrom Number is greater", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + sampleDerivedFrom := types.BlockSeal{Number: 4, Hash: common.BytesToHash([]byte{0x03})} + ssd.derivedFromFn = func() (derivedFrom types.BlockSeal, err error) { + return sampleDerivedFrom, nil + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and CrossDerivedFrom returns a BlockSeal with a greater Number than the inL1DerivedFrom, + // an error is returned as a ErrOutOfScope + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrOutOfScope) + require.Empty(t, hazards) + }) + t.Run("timestamp is less, CrossDerivedFrom Number less", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + sampleDerivedFrom := types.BlockSeal{Number: 1, Hash: common.BytesToHash([]byte{0x03})} + ssd.derivedFromFn = func() (derivedFrom types.BlockSeal, err error) { + return sampleDerivedFrom, nil + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{Number: 10} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and CrossDerivedFrom returns a BlockSeal with a smaller Number than the inL1DerivedFrom, + // no error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.NoError(t, err) + require.Empty(t, hazards) + }) + t.Run("timestamp is less, CrossDerivedFrom Number equal", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + ssd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + sampleDerivedFrom := types.BlockSeal{Number: 1, Hash: common.BytesToHash([]byte{0x03})} + ssd.derivedFromFn = func() (derivedFrom types.BlockSeal, err error) { + return sampleDerivedFrom, nil + } + ssd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + inL1DerivedFrom := eth.BlockID{Number: 1} + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and CrossDerivedFrom returns a BlockSeal with a equal to the Number of inL1DerivedFrom, + // no error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1DerivedFrom, candidate, execMsgs) + require.NoError(t, err) + require.Empty(t, hazards) + }) +} + +type mockSafeStartDeps struct { + deps mockDependencySet + checkFn func() (includedIn types.BlockSeal, err error) + derivedFromFn func() (derivedFrom types.BlockSeal, err error) +} + +func (m *mockSafeStartDeps) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (includedIn types.BlockSeal, err error) { + if m.checkFn != nil { + return m.checkFn() + } + return types.BlockSeal{}, nil +} + +func (m *mockSafeStartDeps) CrossDerivedFrom(chainID types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) { + if m.derivedFromFn != nil { + return m.derivedFromFn() + } + return types.BlockSeal{}, nil +} + +func (m *mockSafeStartDeps) DependencySet() depset.DependencySet { + return m.deps +} diff --git a/op-supervisor/supervisor/backend/cross/safe_update.go b/op-supervisor/supervisor/backend/cross/safe_update.go new file mode 100644 index 0000000000000..bcda7c9f782bc --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/safe_update.go @@ -0,0 +1,116 @@ +package cross + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type CrossSafeDeps interface { + CrossSafe(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) + + SafeFrontierCheckDeps + SafeStartDeps + + CandidateCrossSafe(chain types.ChainID) (derivedFromScope, crossSafe eth.BlockRef, err error) + NextDerivedFrom(chain types.ChainID, derivedFrom eth.BlockID) (after eth.BlockRef, err error) + PreviousDerived(chain types.ChainID, derived eth.BlockID) (prevDerived types.BlockSeal, err error) + + OpenBlock(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) + + UpdateCrossSafe(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error +} + +func CrossSafeUpdate(ctx context.Context, logger log.Logger, chainID types.ChainID, d CrossSafeDeps) error { + logger.Debug("Cross-safe update call") + // TODO(#11693): establish L1 reorg-lock of scopeDerivedFrom + // defer unlock once we are done checking the chain + candidateScope, err := scopedCrossSafeUpdate(logger, chainID, d) + if err == nil { + // if we made progress, and no errors, then there is no need to bump the L1 scope yet. + return nil + } + if !errors.Is(err, types.ErrOutOfScope) { + return err + } + // candidateScope is expected to be set if ErrOutOfScope is returned. + if candidateScope == (eth.BlockRef{}) { + return fmt.Errorf("expected L1 scope to be defined with ErrOutOfScope: %w", err) + } + logger.Debug("Cross-safe updating ran out of L1 scope", "scope", candidateScope, "err", err) + // bump the L1 scope up, and repeat the prev L2 block, not the candidate + newScope, err := d.NextDerivedFrom(chainID, candidateScope.ID()) + if err != nil { + return fmt.Errorf("failed to identify new L1 scope to expand to after %s: %w", candidateScope, err) + } + _, currentCrossSafe, err := d.CrossSafe(chainID) + if err != nil { + // TODO: if genesis isn't cross-safe by default, then we can't register something as cross-safe here + return fmt.Errorf("failed to identify cross-safe scope to repeat: %w", err) + } + parent, err := d.PreviousDerived(chainID, currentCrossSafe.ID()) + if err != nil { + return fmt.Errorf("cannot find parent-block of cross-safe: %w", err) + } + crossSafeRef := currentCrossSafe.MustWithParent(parent.ID()) + logger.Debug("Bumping cross-safe scope", "scope", newScope, "crossSafe", crossSafeRef) + if err := d.UpdateCrossSafe(chainID, newScope, crossSafeRef); err != nil { + return fmt.Errorf("failed to update cross-safe head with L1 scope increment to %s and repeat of L2 block %s: %w", candidateScope, crossSafeRef, err) + } + return nil +} + +// scopedCrossSafeUpdate runs through the cross-safe update checks. +// If no L2 cross-safe progress can be made without additional L1 input data, +// then a types.ErrOutOfScope error is returned, +// with the current scope that will need to be expanded for further progress. +func scopedCrossSafeUpdate(logger log.Logger, chainID types.ChainID, d CrossSafeDeps) (scope eth.BlockRef, err error) { + candidateScope, candidate, err := d.CandidateCrossSafe(chainID) + if err != nil { + return candidateScope, fmt.Errorf("failed to determine candidate block for cross-safe: %w", err) + } + logger.Debug("Candidate cross-safe", "scope", candidateScope, "candidate", candidate) + opened, _, execMsgs, err := d.OpenBlock(chainID, candidate.Number) + if err != nil { + return candidateScope, fmt.Errorf("failed to open block %s: %w", candidate, err) + } + if opened.ID() != candidate.ID() { + return candidateScope, fmt.Errorf("unsafe L2 DB has %s, but candidate cross-safe was %s: %w", opened, candidate, types.ErrConflict) + } + hazards, err := CrossSafeHazards(d, chainID, candidateScope.ID(), types.BlockSealFromRef(opened), sliceOfExecMsgs(execMsgs)) + if err != nil { + return candidateScope, fmt.Errorf("failed to determine dependencies of cross-safe candidate %s: %w", candidate, err) + } + if err := HazardSafeFrontierChecks(d, candidateScope.ID(), hazards); err != nil { + return candidateScope, fmt.Errorf("failed to verify block %s in cross-safe frontier: %w", candidate, err) + } + if err := HazardCycleChecks(d, candidate.Time, hazards); err != nil { + return candidateScope, fmt.Errorf("failed to verify block %s in cross-safe check for cycle hazards: %w", candidate, err) + } + + // promote the candidate block to cross-safe + if err := d.UpdateCrossSafe(chainID, candidateScope, candidate); err != nil { + return candidateScope, fmt.Errorf("failed to update cross-safe head to %s, derived from scope %s: %w", candidate, candidateScope, err) + } + return candidateScope, nil +} + +func NewCrossSafeWorker(logger log.Logger, chainID types.ChainID, d CrossSafeDeps) *Worker { + logger = logger.New("chain", chainID) + return NewWorker(logger, func(ctx context.Context) error { + return CrossSafeUpdate(ctx, logger, chainID, d) + }) +} + +func sliceOfExecMsgs(execMsgs map[uint32]*types.ExecutingMessage) []*types.ExecutingMessage { + msgs := make([]*types.ExecutingMessage, 0, len(execMsgs)) + for _, msg := range execMsgs { + msgs = append(msgs, msg) + } + return msgs +} diff --git a/op-supervisor/supervisor/backend/cross/safe_update_test.go b/op-supervisor/supervisor/backend/cross/safe_update_test.go new file mode 100644 index 0000000000000..3fc0ebba13884 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/safe_update_test.go @@ -0,0 +1,442 @@ +package cross + +import ( + "context" + "errors" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestCrossSafeUpdate(t *testing.T) { + t.Run("scopedCrossSafeUpdate passes", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + opened := eth.BlockRef{Number: 1} + execs := map[uint32]*types.ExecutingMessage{1: {}} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 10, execs, nil + } + csd.checkFn = func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return types.BlockSeal{Number: 1, Timestamp: 1}, nil + } + csd.deps = mockDependencySet{} + // when scopedCrossSafeUpdate returns no error, + // no error is returned + err := CrossSafeUpdate(ctx, logger, chainID, csd) + require.NoError(t, err) + }) + t.Run("scopedCrossSafeUpdate reuturns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return eth.BlockRef{}, 0, nil, errors.New("some error") + } + csd.deps = mockDependencySet{} + // when scopedCrossSafeUpdate returns an error, + // (by way of OpenBlock returning an error), + // the error is returned + err := CrossSafeUpdate(ctx, logger, chainID, csd) + require.ErrorContains(t, err, "some error") + }) + t.Run("scopedCrossSafeUpdate reuturns ErrOutOfScope", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return eth.BlockRef{}, 0, nil, types.ErrOutOfScope + } + newScope := eth.BlockRef{Number: 3} + csd.nextDerivedFromFn = func(chain types.ChainID, derivedFrom eth.BlockID) (after eth.BlockRef, err error) { + return newScope, nil + } + currentCrossSafe := types.BlockSeal{Number: 5} + csd.crossSafeFn = func(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { + return types.BlockSeal{}, currentCrossSafe, nil + } + parent := types.BlockSeal{Number: 4} + csd.previousDerivedFn = func(chain types.ChainID, derived eth.BlockID) (prevDerived types.BlockSeal, err error) { + return parent, nil + } + csd.deps = mockDependencySet{} + var updatingChain types.ChainID + var updatingCandidateScope eth.BlockRef + var updatingCandidate eth.BlockRef + csd.updateCrossSafeFn = func(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error { + updatingChain = chain + updatingCandidateScope = l1View + updatingCandidate = lastCrossDerived + return nil + } + // when scopedCrossSafeUpdate returns Out of Scope error, + // CrossSafeUpdate proceeds anyway and calls UpdateCrossSafe + // the update uses the new scope returned by NextDerivedFrom + // and a crossSafeRef made from the current crossSafe and its parent + err := CrossSafeUpdate(ctx, logger, chainID, csd) + require.NoError(t, err) + require.Equal(t, chainID, updatingChain) + require.Equal(t, newScope, updatingCandidateScope) + crossSafeRef := currentCrossSafe.MustWithParent(parent.ID()) + require.Equal(t, crossSafeRef, updatingCandidate) + }) + t.Run("NextDerivedFrom returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return eth.BlockRef{}, 0, nil, types.ErrOutOfScope + } + csd.nextDerivedFromFn = func(chain types.ChainID, derivedFrom eth.BlockID) (after eth.BlockRef, err error) { + return eth.BlockRef{}, errors.New("some error") + } + csd.deps = mockDependencySet{} + // when scopedCrossSafeUpdate returns Out of Scope error, + // and NextDerivedFrom returns an error, + // the error is returned + err := CrossSafeUpdate(ctx, logger, chainID, csd) + require.ErrorContains(t, err, "some error") + }) + t.Run("PreviousDerived returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return eth.BlockRef{}, 0, nil, types.ErrOutOfScope + } + csd.previousDerivedFn = func(chain types.ChainID, derived eth.BlockID) (prevDerived types.BlockSeal, err error) { + return types.BlockSeal{}, errors.New("some error") + } + csd.deps = mockDependencySet{} + // when scopedCrossSafeUpdate returns Out of Scope error, + // and PreviousDerived returns an error, + // the error is returned + err := CrossSafeUpdate(ctx, logger, chainID, csd) + require.ErrorContains(t, err, "some error") + }) + t.Run("UpdateCrossSafe returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return eth.BlockRef{}, 0, nil, types.ErrOutOfScope + } + csd.updateCrossSafeFn = func(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error { + return errors.New("some error") + } + csd.deps = mockDependencySet{} + // when scopedCrossSafeUpdate returns Out of Scope error, + // and UpdateCrossSafe returns an error, + // the error is returned + err := CrossSafeUpdate(ctx, logger, chainID, csd) + require.ErrorContains(t, err, "some error") + }) +} + +func TestScopedCrossSafeUpdate(t *testing.T) { + t.Run("CandidateCrossSafe returns error", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{}, eth.BlockRef{}, errors.New("some error") + } + // when CandidateCrossSafe returns an error, + // the error is returned + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorContains(t, err, "some error") + require.Equal(t, eth.BlockRef{}, blockRef) + }) + t.Run("CandidateCrossSafe returns error", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return eth.BlockRef{}, 0, nil, errors.New("some error") + } + // when OpenBlock returns an error, + // the error is returned + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorContains(t, err, "some error") + require.Equal(t, eth.BlockRef{}, blockRef) + }) + t.Run("candidate does not match opened block", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{}, candidate, nil + } + opened := eth.BlockRef{Number: 2} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 0, nil, nil + } + // when OpenBlock and CandidateCrossSafe return different blocks, + // an ErrConflict is returned + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorIs(t, err, types.ErrConflict) + require.Equal(t, eth.BlockRef{}, blockRef) + }) + t.Run("CrossSafeHazards returns error", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{}, candidate, nil + } + opened := eth.BlockRef{Number: 1} + execs := map[uint32]*types.ExecutingMessage{1: {}} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 10, execs, nil + } + // cause CrossSafeHazards to return an error by making ChainIDFromIndex return an error + csd.deps = mockDependencySet{} + csd.deps.chainIDFromIndexfn = func() (types.ChainID, error) { + return types.ChainID{}, errors.New("some error") + } + // when CrossSafeHazards returns an error, + // the error is returned + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorContains(t, err, "some error") + require.ErrorContains(t, err, "dependencies of cross-safe candidate") + require.Equal(t, eth.BlockRef{}, blockRef) + }) + t.Run("HazardSafeFrontierChecks returns error", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return eth.BlockRef{}, candidate, nil + } + opened := eth.BlockRef{Number: 1} + execs := map[uint32]*types.ExecutingMessage{1: {}} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 10, execs, nil + } + csd.checkFn = func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return types.BlockSeal{Number: 1, Timestamp: 1}, nil + } + count := 0 + csd.deps = mockDependencySet{} + // cause CrossSafeHazards to return an error by making ChainIDFromIndex return an error + // but only on the second call (which will be used by HazardSafeFrontierChecks) + csd.deps.chainIDFromIndexfn = func() (types.ChainID, error) { + defer func() { count++ }() + if count == 0 { + return types.ChainID{}, nil + } + return types.ChainID{}, errors.New("some error") + } + // when CrossSafeHazards returns an error, + // the error is returned + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorContains(t, err, "some error") + require.ErrorContains(t, err, "frontier") + require.Equal(t, eth.BlockRef{}, blockRef) + }) + t.Run("HazardCycleChecks returns error", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1, Time: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + opened := eth.BlockRef{Number: 1, Time: 1} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1, LogIdx: 2} + em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1, LogIdx: 1} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 3, map[uint32]*types.ExecutingMessage{1: em1, 2: em2}, nil + } + csd.checkFn = func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return types.BlockSeal{Number: 1, Timestamp: 1}, nil + } + csd.deps = mockDependencySet{} + + // HazardCycleChecks returns an error with appropriate wrapping + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorContains(t, err, "cycle detected") + require.ErrorContains(t, err, "failed to verify block") + require.Equal(t, eth.BlockRef{Number: 2}, blockRef) + }) + t.Run("UpdateCrossSafe returns error", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + opened := eth.BlockRef{Number: 1} + execs := map[uint32]*types.ExecutingMessage{1: {}} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 10, execs, nil + } + csd.checkFn = func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return types.BlockSeal{Number: 1, Timestamp: 1}, nil + } + csd.deps = mockDependencySet{} + csd.updateCrossSafeFn = func(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error { + return errors.New("some error") + } + // when UpdateCrossSafe returns an error, + // the error is returned + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorContains(t, err, "some error") + require.ErrorContains(t, err, "failed to update") + require.Equal(t, eth.BlockRef{Number: 2}, blockRef) + }) + t.Run("successful update", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (derivedFromScope, crossSafe eth.BlockRef, err error) { + return candidateScope, candidate, nil + } + opened := eth.BlockRef{Number: 1} + execs := map[uint32]*types.ExecutingMessage{1: {}} + csd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 10, execs, nil + } + csd.deps = mockDependencySet{} + var updatingChain types.ChainID + var updatingCandidateScope eth.BlockRef + var updatingCandidate eth.BlockRef + csd.updateCrossSafeFn = func(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error { + updatingChain = chain + updatingCandidateScope = l1View + updatingCandidate = lastCrossDerived + return nil + } + // when no errors occur, the update is carried out + // the used candidate and scope are from CandidateCrossSafe + // the candidateScope is returned + csd.checkFn = func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return types.BlockSeal{Number: 1, Timestamp: 1}, nil + } + blockRef, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.Equal(t, chainID, updatingChain) + require.Equal(t, candidateScope, updatingCandidateScope) + require.Equal(t, candidate, updatingCandidate) + require.Equal(t, candidateScope, blockRef) + require.NoError(t, err) + }) +} + +type mockCrossSafeDeps struct { + deps mockDependencySet + crossSafeFn func(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) + candidateCrossSafeFn func() (derivedFromScope, crossSafe eth.BlockRef, err error) + openBlockFn func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) + updateCrossSafeFn func(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error + nextDerivedFromFn func(chain types.ChainID, derivedFrom eth.BlockID) (after eth.BlockRef, err error) + previousDerivedFn func(chain types.ChainID, derived eth.BlockID) (prevDerived types.BlockSeal, err error) + checkFn func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) +} + +func (m *mockCrossSafeDeps) CrossSafe(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { + if m.crossSafeFn != nil { + return m.crossSafeFn(chainID) + } + return types.BlockSeal{}, types.BlockSeal{}, nil +} + +func (m *mockCrossSafeDeps) CandidateCrossSafe(chain types.ChainID) (derivedFromScope, crossSafe eth.BlockRef, err error) { + if m.candidateCrossSafeFn != nil { + return m.candidateCrossSafeFn() + } + return eth.BlockRef{}, eth.BlockRef{}, nil +} + +func (m *mockCrossSafeDeps) DependencySet() depset.DependencySet { + return m.deps +} + +func (m *mockCrossSafeDeps) CrossDerivedFrom(chainID types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) { + return types.BlockSeal{}, nil +} + +func (m *mockCrossSafeDeps) Check(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + if m.checkFn != nil { + return m.checkFn(chainID, blockNum, logIdx, logHash) + } + return types.BlockSeal{}, nil +} + +func (m *mockCrossSafeDeps) NextDerivedFrom(chain types.ChainID, derivedFrom eth.BlockID) (after eth.BlockRef, err error) { + if m.nextDerivedFromFn != nil { + return m.nextDerivedFromFn(chain, derivedFrom) + } + return eth.BlockRef{}, nil +} + +func (m *mockCrossSafeDeps) PreviousDerived(chain types.ChainID, derived eth.BlockID) (prevDerived types.BlockSeal, err error) { + if m.previousDerivedFn != nil { + return m.previousDerivedFn(chain, derived) + } + return types.BlockSeal{}, nil +} + +func (m *mockCrossSafeDeps) OpenBlock(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + if m.openBlockFn != nil { + return m.openBlockFn(chainID, blockNum) + } + return eth.BlockRef{}, 0, nil, nil +} + +func (m *mockCrossSafeDeps) UpdateCrossSafe(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error { + if m.updateCrossSafeFn != nil { + return m.updateCrossSafeFn(chain, l1View, lastCrossDerived) + } + return nil +} diff --git a/op-supervisor/supervisor/backend/cross/unsafe_frontier.go b/op-supervisor/supervisor/backend/cross/unsafe_frontier.go new file mode 100644 index 0000000000000..5e30da999eb13 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/unsafe_frontier.go @@ -0,0 +1,63 @@ +package cross + +import ( + "errors" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type UnsafeFrontierCheckDeps interface { + ParentBlock(chainID types.ChainID, parentOf eth.BlockID) (parent eth.BlockID, err error) + + IsCrossUnsafe(chainID types.ChainID, block eth.BlockID) error + IsLocalUnsafe(chainID types.ChainID, block eth.BlockID) error + + DependencySet() depset.DependencySet +} + +// HazardUnsafeFrontierChecks verifies all the hazard blocks are either: +// - already cross-unsafe. +// - the first (if not first: local blocks to verify before proceeding) +// local-unsafe block, after the cross-unsafe block. +func HazardUnsafeFrontierChecks(d UnsafeFrontierCheckDeps, hazards map[types.ChainIndex]types.BlockSeal) error { + depSet := d.DependencySet() + for hazardChainIndex, hazardBlock := range hazards { + hazardChainID, err := depSet.ChainIDFromIndex(hazardChainIndex) + if err != nil { + if errors.Is(err, types.ErrUnknownChain) { + err = fmt.Errorf("cannot cross-unsafe verify block %s of unknown chain index %s: %w", hazardBlock, hazardChainIndex, types.ErrConflict) + } + return err + } + // Anything we depend on in this timestamp must be cross-unsafe already, or the first block after. + err = d.IsCrossUnsafe(hazardChainID, hazardBlock.ID()) + if err != nil { + if errors.Is(err, types.ErrFuture) { + // Not already cross-unsafe, so we check if the block is local-unsafe + // (a sanity check if part of the canonical chain). + err = d.IsLocalUnsafe(hazardChainID, hazardBlock.ID()) + if err != nil { + // can be ErrFuture (missing data) or ErrConflict (non-canonical) + return fmt.Errorf("hazard block %s (chain %d) is not local-unsafe: %w", hazardBlock, hazardChainID, err) + } + // If it doesn't have a parent block, then there is no prior block required to be cross-safe + if hazardBlock.Number > 0 { + // Check that parent of hazardBlockID is cross-safe within view + parent, err := d.ParentBlock(hazardChainID, hazardBlock.ID()) + if err != nil { + return fmt.Errorf("failed to retrieve parent-block of hazard block %s (chain %s): %w", hazardBlock, hazardChainID, err) + } + if err := d.IsCrossUnsafe(hazardChainID, parent); err != nil { + return fmt.Errorf("cannot rely on hazard-block %s (chain %s), parent block %s is not cross-unsafe: %w", hazardBlock, hazardChainID, parent, err) + } + } + } else { + return fmt.Errorf("failed to determine cross-derived of hazard block %s (chain %s): %w", hazardBlock, hazardChainID, err) + } + } + } + return nil +} diff --git a/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go b/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go new file mode 100644 index 0000000000000..09086e0e87385 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go @@ -0,0 +1,130 @@ +package cross + +import ( + "errors" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestHazardUnsafeFrontierChecks(t *testing.T) { + t.Run("empty hazards", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{} + hazards := map[types.ChainIndex]types.BlockSeal{} + // when there are no hazards, + // no work is done, and no error is returned + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.NoError(t, err) + }) + t.Run("unknown chain", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{ + deps: mockDependencySet{ + chainIDFromIndexfn: func() (types.ChainID, error) { + return types.ChainID{}, types.ErrUnknownChain + }, + }, + } + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + // when there is one hazard, and ChainIDFromIndex returns ErrUnknownChain, + // an error is returned as a ErrConflict + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.ErrorIs(t, err, types.ErrConflict) + }) + t.Run("is cross unsafe", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + ufcd.isCrossUnsafe = nil + // when there is one hazard, and IsCrossUnsafe returns nil (no error) + // no error is returned + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.NoError(t, err) + }) + t.Run("errFuture: is not local unsafe", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + ufcd.isCrossUnsafe = types.ErrFuture + ufcd.isLocalUnsafe = errors.New("some error") + // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, + // and IsLocalUnsafe returns an error, + // the error from IsLocalUnsafe is (wrapped and) returned + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.ErrorContains(t, err, "some error") + }) + t.Run("errFuture: genesis block", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + ufcd.isCrossUnsafe = types.ErrFuture + // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, + // BUT the hazard's block number is 0, + // no error is returned + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.NoError(t, err) + }) + t.Run("errFuture: error getting parent block", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3}} + ufcd.isCrossUnsafe = types.ErrFuture + ufcd.parentBlockFn = func() (parent eth.BlockID, err error) { + return eth.BlockID{}, errors.New("some error") + } + // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, + // and there is an error getting the parent block, + // the error from ParentBlock is (wrapped and) returned + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.ErrorContains(t, err, "some error") + }) + t.Run("errFuture: parent block is not cross unsafe", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3}} + ufcd.isCrossUnsafe = types.ErrFuture + ufcd.parentBlockFn = func() (parent eth.BlockID, err error) { + // when getting the parent block, prep isCrossSafe to be err + ufcd.isCrossUnsafe = errors.New("not cross unsafe!") + return eth.BlockID{}, nil + } + // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, + // and the parent block is not cross unsafe, + // the error from IsCrossUnsafe is (wrapped and) returned + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.ErrorContains(t, err, "not cross unsafe!") + }) + t.Run("IsCrossUnsafe Error", func(t *testing.T) { + ufcd := &mockUnsafeFrontierCheckDeps{} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3, Hash: common.BytesToHash([]byte{0x02})}} + ufcd.isCrossUnsafe = errors.New("some error") + // when there is one hazard, and IsCrossUnsafe returns an error, + // the error from IsCrossUnsafe is (wrapped and) returned + err := HazardUnsafeFrontierChecks(ufcd, hazards) + require.ErrorContains(t, err, "some error") + }) +} + +type mockUnsafeFrontierCheckDeps struct { + deps mockDependencySet + parentBlockFn func() (parent eth.BlockID, err error) + isCrossUnsafe error + isLocalUnsafe error +} + +func (m *mockUnsafeFrontierCheckDeps) DependencySet() depset.DependencySet { + return m.deps +} + +func (m *mockUnsafeFrontierCheckDeps) ParentBlock(chainID types.ChainID, block eth.BlockID) (parent eth.BlockID, err error) { + if m.parentBlockFn != nil { + return m.parentBlockFn() + } + return eth.BlockID{}, nil +} + +func (m *mockUnsafeFrontierCheckDeps) IsCrossUnsafe(chainID types.ChainID, block eth.BlockID) error { + return m.isCrossUnsafe +} + +func (m *mockUnsafeFrontierCheckDeps) IsLocalUnsafe(chainID types.ChainID, block eth.BlockID) error { + return m.isLocalUnsafe +} diff --git a/op-supervisor/supervisor/backend/cross/unsafe_start.go b/op-supervisor/supervisor/backend/cross/unsafe_start.go new file mode 100644 index 0000000000000..9b4568a7b6225 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/unsafe_start.go @@ -0,0 +1,100 @@ +package cross + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type UnsafeStartDeps interface { + Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (includedIn types.BlockSeal, err error) + + IsCrossUnsafe(chainID types.ChainID, block eth.BlockID) error + + DependencySet() depset.DependencySet +} + +// CrossUnsafeHazards checks if the given messages all exist and pass invariants. +// It returns a hazard-set: if any intra-block messaging happened, +// these hazard blocks have to be verified. +func CrossUnsafeHazards(d UnsafeStartDeps, chainID types.ChainID, + candidate types.BlockSeal, execMsgs []*types.ExecutingMessage) (hazards map[types.ChainIndex]types.BlockSeal, err error) { + + hazards = make(map[types.ChainIndex]types.BlockSeal) + + // Warning for future: If we have sub-second distinct blocks (different block number), + // we need to increase precision on the above timestamp invariant. + // Otherwise a local block can depend on a future local block of the same chain, + // simply by pulling in a block of another chain, + // which then depends on a block of the original chain, + // all with the same timestamp, without message cycles. + + depSet := d.DependencySet() + + if len(execMsgs) > 0 { + if ok, err := depSet.CanExecuteAt(chainID, candidate.Timestamp); err != nil { + return nil, fmt.Errorf("cannot check message execution of block %s (chain %s): %w", candidate, chainID, err) + } else if !ok { + return nil, fmt.Errorf("cannot execute messages in block %s (chain %s): %w", candidate, chainID, types.ErrConflict) + } + } + + // check all executing messages + for _, msg := range execMsgs { + initChainID, err := depSet.ChainIDFromIndex(msg.Chain) + if err != nil { + if errors.Is(err, types.ErrUnknownChain) { + err = fmt.Errorf("msg %s may not execute from unknown chain %s: %w", msg, msg.Chain, types.ErrConflict) + } + return nil, err + } + if ok, err := depSet.CanInitiateAt(initChainID, msg.Timestamp); err != nil { + return nil, fmt.Errorf("cannot check message initiation of msg %s (chain %s): %w", msg, chainID, err) + } else if !ok { + return nil, fmt.Errorf("cannot allow initiating message %s (chain %s): %w", msg, chainID, types.ErrConflict) + } + if msg.Timestamp < candidate.Timestamp { + // If timestamp is older: invariant ensures non-cyclic ordering relative to other messages. + // Check that the block that they are included in is cross-safe already. + includedIn, err := d.Check(initChainID, msg.BlockNum, msg.LogIdx, msg.Hash) + if err != nil { + return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) + } + if err := d.IsCrossUnsafe(initChainID, includedIn.ID()); err != nil { + return nil, fmt.Errorf("msg %s included in non-cross-unsafe block %s: %w", msg, includedIn, err) + } + } else if msg.Timestamp == candidate.Timestamp { + // If timestamp is equal: we have to inspect ordering of individual + // log events to ensure non-cyclic cross-chain message ordering. + // And since we may have back-and-forth messaging, we cannot wait till the initiating side is cross-unsafe. + // Thus check that it was included in a local-unsafe block, + // and then proceed with transitive block checks, + // to ensure the local block we depend on is becoming cross-unsafe also. + includedIn, err := d.Check(initChainID, msg.BlockNum, msg.LogIdx, msg.Hash) + if err != nil { + return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) + } + + // As a hazard block, it will be checked to be included in a cross-unsafe block, + // or right after a cross-unsafe block, in HazardUnsafeFrontierChecks. + if existing, ok := hazards[msg.Chain]; ok { + if existing != includedIn { + return nil, fmt.Errorf("found dependency on %s (chain %d), but already depend on %s", includedIn, initChainID, chainID) + } + } else { + // Mark it as hazard block + hazards[msg.Chain] = includedIn + } + } else { + // Timestamp invariant is broken: executing message tries to execute future block. + // The predeploy inbox contract should not have allowed this executing message through. + return nil, fmt.Errorf("executing message %s in %s breaks timestamp invariant", msg, candidate) + } + } + return hazards, nil +} diff --git a/op-supervisor/supervisor/backend/cross/unsafe_start_test.go b/op-supervisor/supervisor/backend/cross/unsafe_start_test.go new file mode 100644 index 0000000000000..123fc70abe17d --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/unsafe_start_test.go @@ -0,0 +1,280 @@ +package cross + +import ( + "errors" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestCrossUnsafeHazards(t *testing.T) { + t.Run("empty execMsgs", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{} + // when there are no execMsgs, + // no work is done, and no error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.NoError(t, err) + require.Empty(t, hazards) + }) + t.Run("CanExecuteAt returns false", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps = mockDependencySet{ + canExecuteAtfn: func() (bool, error) { + return false, nil + }, + } + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanExecuteAt returns false, + // no work is done and an error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.Empty(t, hazards) + }) + t.Run("CanExecuteAt returns error", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps = mockDependencySet{ + canExecuteAtfn: func() (bool, error) { + return false, errors.New("some error") + }, + } + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanExecuteAt returns false, + // no work is done and an error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("unknown chain", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps = mockDependencySet{ + chainIDFromIndexfn: func() (types.ChainID, error) { + return types.ChainID{}, types.ErrUnknownChain + }, + } + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and ChainIDFromIndex returns ErrUnknownChain, + // an error is returned as a ErrConflict + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.Empty(t, hazards) + }) + t.Run("ChainIDFromUInt64 returns error", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps = mockDependencySet{ + chainIDFromIndexfn: func() (types.ChainID, error) { + return types.ChainID{}, errors.New("some error") + }, + } + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and ChainIDFromIndex returns some other error, + // the error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("CanInitiateAt returns false", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps = mockDependencySet{ + canInitiateAtfn: func() (bool, error) { + return false, nil + }, + } + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanInitiateAt returns false, + // the error is returned as a ErrConflict + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.Empty(t, hazards) + }) + t.Run("CanInitiateAt returns error", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps = mockDependencySet{ + canInitiateAtfn: func() (bool, error) { + return false, errors.New("some error") + }, + } + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{} + execMsgs := []*types.ExecutingMessage{{}} + // when there is one execMsg, and CanInitiateAt returns an error, + // the error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is greater than candidate", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 10} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is greater than the candidate, + // an error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "breaks timestamp invariant") + require.Empty(t, hazards) + }) + t.Run("timestamp is equal, Check returns error", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + return types.BlockSeal{}, errors.New("some error") + } + usd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timetamp is equal to the candidate, + // and check returns an error, + // that error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is equal, same hazard twice", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + usd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + execMsgs := []*types.ExecutingMessage{em1, em2} + // when there are two execMsgs, and both are equal time to the candidate, + // and check returns the same includedIn for both + // they load the hazards once, and return no error + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.NoError(t, err) + require.Equal(t, hazards, map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): sampleBlockSeal}) + }) + t.Run("timestamp is equal, different hazards", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + // set the check function to return a different BlockSeal for the second call + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + sampleBlockSeal2 := types.BlockSeal{Number: 333, Hash: common.BytesToHash([]byte{0x22})} + calls := 0 + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + defer func() { calls++ }() + if calls == 0 { + return sampleBlockSeal, nil + } + return sampleBlockSeal2, nil + } + usd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} + execMsgs := []*types.ExecutingMessage{em1, em2} + // when there are two execMsgs, and both are equal time to the candidate, + // and check returns different includedIn for the two, + // an error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "but already depend on") + require.Empty(t, hazards) + }) + t.Run("timestamp is less, check returns error", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + return types.BlockSeal{}, errors.New("some error") + } + usd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and check returns an error, + // that error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is less, IsCrossUnsafe returns error", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + usd.isCrossUnsafeFn = func() error { + return errors.New("some error") + } + usd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and IsCrossUnsafe returns an error, + // that error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorContains(t, err, "some error") + require.Empty(t, hazards) + }) + t.Run("timestamp is less, IsCrossUnsafe", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + usd.isCrossUnsafeFn = func() error { + return nil + } + usd.deps = mockDependencySet{} + chainID := types.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 2} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and IsCrossUnsafe returns no error, + // no error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.NoError(t, err) + require.Empty(t, hazards) + }) +} + +type mockUnsafeStartDeps struct { + deps mockDependencySet + checkFn func() (includedIn types.BlockSeal, err error) + isCrossUnsafeFn func() error +} + +func (m *mockUnsafeStartDeps) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (includedIn types.BlockSeal, err error) { + if m.checkFn != nil { + return m.checkFn() + } + return types.BlockSeal{}, nil +} + +func (m *mockUnsafeStartDeps) IsCrossUnsafe(chainID types.ChainID, derived eth.BlockID) error { + if m.isCrossUnsafeFn != nil { + return m.isCrossUnsafeFn() + } + return nil +} + +func (m *mockUnsafeStartDeps) DependencySet() depset.DependencySet { + return m.deps +} diff --git a/op-supervisor/supervisor/backend/cross/unsafe_update.go b/op-supervisor/supervisor/backend/cross/unsafe_update.go new file mode 100644 index 0000000000000..56d3961271585 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/unsafe_update.go @@ -0,0 +1,79 @@ +package cross + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type CrossUnsafeDeps interface { + CrossUnsafe(chainID types.ChainID) (types.BlockSeal, error) + + UnsafeStartDeps + UnsafeFrontierCheckDeps + + OpenBlock(chainID types.ChainID, blockNum uint64) (block eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) + + UpdateCrossUnsafe(chain types.ChainID, crossUnsafe types.BlockSeal) error +} + +func CrossUnsafeUpdate(ctx context.Context, logger log.Logger, chainID types.ChainID, d CrossUnsafeDeps) error { + var candidate types.BlockSeal + var execMsgs []*types.ExecutingMessage + + // fetch cross-head to determine next cross-unsafe candidate + if crossUnsafe, err := d.CrossUnsafe(chainID); err != nil { + if errors.Is(err, types.ErrFuture) { + // If genesis / no cross-safe block yet, then defer update + logger.Debug("No cross-unsafe starting point yet") + return nil + } else { + return err + } + } else { + // Open block N+1: this is a local-unsafe block, + // just after cross-safe, that can be promoted if it passes the dependency checks. + bl, _, msgs, err := d.OpenBlock(chainID, crossUnsafe.Number+1) + if err != nil { + return fmt.Errorf("failed to open block %d: %w", crossUnsafe.Number+1, err) + } + if bl.ParentHash != crossUnsafe.Hash { + return fmt.Errorf("cannot use block %s, it does not build on cross-unsafe block %s: %w", bl, crossUnsafe, types.ErrConflict) + } + candidate = types.BlockSealFromRef(bl) + execMsgs = sliceOfExecMsgs(msgs) + } + + hazards, err := CrossUnsafeHazards(d, chainID, candidate, execMsgs) + if err != nil { + // TODO(#11693): reorgs can be detected by checking if the error is ErrConflict, + // missing data is identified by ErrFuture, + // and other errors (e.g. DB issues) are identifier by remaining error kinds. + return fmt.Errorf("failed to check for cross-chain hazards: %w", err) + } + + if err := HazardUnsafeFrontierChecks(d, hazards); err != nil { + return fmt.Errorf("failed to verify block %s in cross-unsafe frontier: %w", candidate, err) + } + if err := HazardCycleChecks(d, candidate.Timestamp, hazards); err != nil { + return fmt.Errorf("failed to verify block %s in cross-unsafe check for cycle hazards: %w", candidate, err) + } + + // promote the candidate block to cross-unsafe + if err := d.UpdateCrossUnsafe(chainID, candidate); err != nil { + return fmt.Errorf("failed to update cross-unsafe head to %s: %w", candidate, err) + } + return nil +} + +func NewCrossUnsafeWorker(logger log.Logger, chainID types.ChainID, d CrossUnsafeDeps) *Worker { + logger = logger.New("chain", chainID) + return NewWorker(logger, func(ctx context.Context) error { + return CrossUnsafeUpdate(ctx, logger, chainID, d) + }) +} diff --git a/op-supervisor/supervisor/backend/cross/unsafe_update_test.go b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go new file mode 100644 index 0000000000000..ada7d0756cf46 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go @@ -0,0 +1,241 @@ +package cross + +import ( + "context" + "errors" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestCrossUnsafeUpdate(t *testing.T) { + t.Run("CrossUnsafe returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + usd.crossUnsafeFn = func(chainID types.ChainID) (types.BlockSeal, error) { + return types.BlockSeal{}, errors.New("some error") + } + usd.deps = mockDependencySet{} + // when an error is returned by CrossUnsafe, + // the error is returned + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.ErrorContains(t, err, "some error") + }) + t.Run("CrossUnsafe returns ErrFuture", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + usd.crossUnsafeFn = func(chainID types.ChainID) (types.BlockSeal, error) { + return types.BlockSeal{}, types.ErrFuture + } + usd.deps = mockDependencySet{} + // when a ErrFuture is returned by CrossUnsafe, + // no error is returned + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.NoError(t, err) + }) + t.Run("OpenBlock returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + usd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return eth.BlockRef{}, 0, nil, errors.New("some error") + } + usd.deps = mockDependencySet{} + // when an error is returned by OpenBlock, + // the error is returned + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.ErrorContains(t, err, "some error") + }) + t.Run("opened block parent hash does not match", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + crossUnsafe := types.BlockSeal{Hash: common.Hash{0x11}} + usd.crossUnsafeFn = func(chainID types.ChainID) (types.BlockSeal, error) { + return crossUnsafe, nil + } + bl := eth.BlockRef{ParentHash: common.Hash{0x01}} + usd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return bl, 0, nil, nil + } + usd.deps = mockDependencySet{} + // when the parent hash of the opened block does not match the cross-unsafe block, + // an ErrConflict is returned + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.ErrorIs(t, err, types.ErrConflict) + }) + t.Run("CrossSafeHazards returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + crossUnsafe := types.BlockSeal{Hash: common.Hash{0x01}} + usd.crossUnsafeFn = func(chainID types.ChainID) (types.BlockSeal, error) { + return crossUnsafe, nil + } + bl := eth.BlockRef{ParentHash: common.Hash{0x01}} + usd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + // include one executing message to trigger the CanExecuteAt check + return bl, 0, map[uint32]*types.ExecutingMessage{1: {}}, nil + } + usd.deps = mockDependencySet{} + // make CrossSafeHazards return an error by setting CanExecuteAtfn to return an error + usd.deps.canExecuteAtfn = func() (bool, error) { + return false, errors.New("some error") + } + // when CrossSafeHazards returns an error, + // the error is returned + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.ErrorContains(t, err, "some error") + }) + t.Run("HazardUnsafeFrontierChecks returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + crossUnsafe := types.BlockSeal{Hash: common.Hash{0x01}} + usd.crossUnsafeFn = func(chainID types.ChainID) (types.BlockSeal, error) { + return crossUnsafe, nil + } + bl := eth.BlockRef{ParentHash: common.Hash{0x01}, Time: 1} + em1 := &types.ExecutingMessage{Timestamp: 1} + usd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + // include one executing message to ensure one hazard is returned + return bl, 0, map[uint32]*types.ExecutingMessage{1: em1}, nil + } + usd.deps = mockDependencySet{} + count := 0 + // make HazardUnsafeFrontierChecks return an error by failing the second ChainIDFromIndex call + // (the first one is in CrossSafeHazards) + usd.deps.chainIDFromIndexfn = func() (types.ChainID, error) { + defer func() { count++ }() + if count == 1 { + return types.ChainID{}, errors.New("some error") + } + return types.ChainID{}, nil + } + // when HazardUnsafeFrontierChecks returns an error, + // the error is returned + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.ErrorContains(t, err, "some error") + }) + t.Run("HazardCycleChecks returns error", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + crossUnsafe := types.BlockSeal{Hash: common.Hash{0x01}} + usd.crossUnsafeFn = func(chainID types.ChainID) (types.BlockSeal, error) { + return crossUnsafe, nil + } + bl := eth.BlockRef{ParentHash: common.Hash{0x01}, Number: 1, Time: 1} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1, LogIdx: 2} + em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1, LogIdx: 1} + usd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return bl, 3, map[uint32]*types.ExecutingMessage{1: em1, 2: em2}, nil + } + usd.checkFn = func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return types.BlockSeal{Number: 1, Timestamp: 1}, nil + } + usd.deps = mockDependencySet{} + + // HazardCycleChecks returns an error with appropriate wrapping + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.ErrorContains(t, err, "cycle detected") + require.ErrorContains(t, err, "failed to verify block") + }) + t.Run("successful update", func(t *testing.T) { + ctx := context.Background() + logger := testlog.Logger(t, log.LevelDebug) + chainID := types.ChainIDFromUInt64(0) + usd := &mockCrossUnsafeDeps{} + crossUnsafe := types.BlockSeal{Hash: common.Hash{0x01}} + usd.crossUnsafeFn = func(chainID types.ChainID) (types.BlockSeal, error) { + return crossUnsafe, nil + } + bl := eth.BlockRef{ParentHash: common.Hash{0x01}, Time: 1} + em1 := &types.ExecutingMessage{Timestamp: 1} + usd.openBlockFn = func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + // include one executing message to ensure one hazard is returned + return bl, 2, map[uint32]*types.ExecutingMessage{1: em1}, nil + } + usd.deps = mockDependencySet{} + var updatingChainID types.ChainID + var updatingBlock types.BlockSeal + usd.updateCrossUnsafeFn = func(chain types.ChainID, crossUnsafe types.BlockSeal) error { + updatingChainID = chain + updatingBlock = crossUnsafe + return nil + } + // when there are no errors, the cross-unsafe block is updated + // the updated block is the block opened in OpenBlock + err := CrossUnsafeUpdate(ctx, logger, chainID, usd) + require.NoError(t, err) + require.Equal(t, chainID, updatingChainID) + require.Equal(t, types.BlockSealFromRef(bl), updatingBlock) + }) +} + +type mockCrossUnsafeDeps struct { + deps mockDependencySet + crossUnsafeFn func(chainID types.ChainID) (types.BlockSeal, error) + openBlockFn func(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) + updateCrossUnsafeFn func(chain types.ChainID, crossUnsafe types.BlockSeal) error + checkFn func(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) +} + +func (m *mockCrossUnsafeDeps) CrossUnsafe(chainID types.ChainID) (derived types.BlockSeal, err error) { + if m.crossUnsafeFn != nil { + return m.crossUnsafeFn(chainID) + } + return types.BlockSeal{}, nil +} + +func (m *mockCrossUnsafeDeps) DependencySet() depset.DependencySet { + return m.deps +} + +func (m *mockCrossUnsafeDeps) Check(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + if m.checkFn != nil { + return m.checkFn(chainID, blockNum, logIdx, logHash) + } + return types.BlockSeal{}, nil +} + +func (m *mockCrossUnsafeDeps) OpenBlock(chainID types.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + if m.openBlockFn != nil { + return m.openBlockFn(chainID, blockNum) + } + return eth.BlockRef{}, 0, nil, nil +} + +func (m *mockCrossUnsafeDeps) UpdateCrossUnsafe(chain types.ChainID, block types.BlockSeal) error { + if m.updateCrossUnsafeFn != nil { + return m.updateCrossUnsafeFn(chain, block) + } + return nil +} + +func (m *mockCrossUnsafeDeps) IsCrossUnsafe(chainID types.ChainID, blockNum eth.BlockID) error { + return nil +} + +func (m *mockCrossUnsafeDeps) IsLocalUnsafe(chainID types.ChainID, blockNum eth.BlockID) error { + return nil +} + +func (m *mockCrossUnsafeDeps) ParentBlock(chainID types.ChainID, blockNum eth.BlockID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} diff --git a/op-supervisor/supervisor/backend/cross/worker.go b/op-supervisor/supervisor/backend/cross/worker.go new file mode 100644 index 0000000000000..689b89829e4b9 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/worker.go @@ -0,0 +1,107 @@ +package cross + +import ( + "context" + "errors" + "sync" + "time" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/log" +) + +// Worker iterates work +type Worker struct { + log log.Logger + + // workFn is the function to call to process the scope + workFn workFn + + // channel with capacity of 1, full if there is work to do + poke chan struct{} + pollDuration time.Duration + + // lifetime management of the chain processor + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup +} + +// workFn is a function used by the worker +// it is opaque to the worker, and is set by the constructor +type workFn func(ctx context.Context) error + +// NewWorker creates a new worker to process updates +func NewWorker(log log.Logger, workFn workFn) *Worker { + ctx, cancel := context.WithCancel(context.Background()) + out := &Worker{ + log: log, + poke: make(chan struct{}, 1), + // The data may have changed, and we may have missed a poke, so re-attempt regularly. + pollDuration: 250 * time.Millisecond, + ctx: ctx, + cancel: cancel, + } + out.workFn = workFn + return out +} + +func (s *Worker) StartBackground() { + s.wg.Add(1) + go s.worker() +} + +func (s *Worker) ProcessWork() error { + return s.workFn(s.ctx) +} + +func (s *Worker) worker() { + defer s.wg.Done() + + delay := time.NewTicker(s.pollDuration) + for { + if s.ctx.Err() != nil { // check if we are closing down + return + } + + // do the work + err := s.workFn(s.ctx) + if err != nil { + if errors.Is(err, s.ctx.Err()) { + return + } + if errors.Is(err, types.ErrFuture) { + s.log.Debug("Worker awaits additional blocks", "err", err) + } else { + s.log.Warn("Failed to process work", "err", err) + } + } + + // await next time we process, or detect shutdown + select { + case <-s.ctx.Done(): + delay.Stop() + return + case <-s.poke: + s.log.Debug("Continuing cross-safe verification after hint of new data") + continue + case <-delay.C: + s.log.Debug("Checking for cross-safe updates") + continue + } + } +} + +func (s *Worker) OnNewData() { + // signal that we have something to process + select { + case s.poke <- struct{}{}: + default: + // already requested an update + } +} + +func (s *Worker) Close() { + s.cancel() + s.wg.Wait() +} diff --git a/op-supervisor/supervisor/backend/cross/worker_test.go b/op-supervisor/supervisor/backend/cross/worker_test.go new file mode 100644 index 0000000000000..6f9d543fde1aa --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/worker_test.go @@ -0,0 +1,107 @@ +package cross + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestWorker(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + t.Run("do work", func(t *testing.T) { + var count int32 + w := NewWorker(logger, func(ctx context.Context) error { + atomic.AddInt32(&count, 1) + return nil + }) + t.Cleanup(w.Close) + // when ProcessWork is called, the workFn is called once + require.NoError(t, w.ProcessWork()) + require.EqualValues(t, 1, atomic.LoadInt32(&count)) + }) + t.Run("background worker", func(t *testing.T) { + var count int32 + w := NewWorker(logger, func(ctx context.Context) error { + atomic.AddInt32(&count, 1) + return nil + }) + t.Cleanup(w.Close) + // set a long poll duration so the worker does not auto-run + w.pollDuration = 100 * time.Second + // when StartBackground is called, the worker runs in the background + // the count should increment once + w.StartBackground() + require.Eventually(t, func() bool { + return atomic.LoadInt32(&count) == 1 + }, 2*time.Second, 100*time.Millisecond) + }) + t.Run("background worker OnNewData", func(t *testing.T) { + var count int32 + w := NewWorker(logger, func(ctx context.Context) error { + atomic.AddInt32(&count, 1) + return nil + }) + t.Cleanup(w.Close) + // set a long poll duration so the worker does not auto-run + w.pollDuration = 100 * time.Second + // when StartBackground is called, the worker runs in the background + // the count should increment once + w.StartBackground() + require.Eventually(t, func() bool { + return atomic.LoadInt32(&count) == 1 + }, 2*time.Second, 100*time.Millisecond) + // when OnNewData is called, the worker runs again + w.OnNewData() + require.Eventually(t, func() bool { + return atomic.LoadInt32(&count) == 2 + }, 2*time.Second, 100*time.Millisecond) + // and due to the long poll duration, the worker does not run again + require.Never(t, func() bool { + return atomic.LoadInt32(&count) > 2 + }, time.Second, 100*time.Millisecond) + }) + t.Run("background fast poll", func(t *testing.T) { + var count int32 + w := NewWorker(logger, func(ctx context.Context) error { + atomic.AddInt32(&count, 1) + return nil + }) + t.Cleanup(w.Close) + // set a long poll duration so the worker does not auto-run + w.pollDuration = 100 * time.Millisecond + // when StartBackground is called, the worker runs in the background + // the count should increment rapidly and reach at least 10 in 1 second + w.StartBackground() + require.Eventually(t, func() bool { + return atomic.LoadInt32(&count) >= 10 + }, 2*time.Second, 100*time.Millisecond) + }) + t.Run("close", func(t *testing.T) { + var count int32 + w := NewWorker(logger, func(ctx context.Context) error { + atomic.AddInt32(&count, 1) + return nil + }) + t.Cleanup(w.Close) // close on cleanup in case of early error + // set a long poll duration so the worker does not auto-run + w.pollDuration = 100 * time.Millisecond + // when StartBackground is called, the worker runs in the background + // the count should increment rapidly and reach at least 10 in 1 second + w.StartBackground() + require.Eventually(t, func() bool { + return atomic.LoadInt32(&count) >= 10 + }, 10*time.Second, time.Second) + // once the worker is closed, it stops running + // and the count does not increment + w.Close() + stopCount := atomic.LoadInt32(&count) + require.Never(t, func() bool { + return atomic.LoadInt32(&count) != stopCount + }, time.Second, 100*time.Millisecond) + }) +} diff --git a/op-supervisor/supervisor/backend/db/db.go b/op-supervisor/supervisor/backend/db/db.go index c4f8296d1ce01..b667718759b79 100644 --- a/op-supervisor/supervisor/backend/db/db.go +++ b/op-supervisor/supervisor/backend/db/db.go @@ -9,16 +9,13 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" + "github.com/ethereum-optimism/optimism/op-service/locks" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/fromda" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/safety" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) -var ( - ErrUnknownChain = errors.New("unknown chain") -) - type LogStorage interface { io.Closer @@ -31,172 +28,147 @@ type LogStorage interface { LatestSealedBlockNum() (n uint64, ok bool) - // FindSealedBlock finds the requested block, to check if it exists, - // returning the next index after it where things continue from. - // returns ErrFuture if the block is too new to be able to tell - // returns ErrDifferent if the known block does not match - FindSealedBlock(block eth.BlockID) (nextEntry entrydb.EntryIdx, err error) + // FindSealedBlock finds the requested block by number, to check if it exists, + // returning the block seal if it was found. + // returns ErrFuture if the block is too new to be able to tell. + FindSealedBlock(number uint64) (block types.BlockSeal, err error) IteratorStartingAt(sealedNum uint64, logsSince uint32) (logs.Iterator, error) - // returns ErrConflict if the log does not match the canonical chain. - // returns ErrFuture if the log is out of reach. - // returns nil if the log is known and matches the canonical chain. - Contains(blockNum uint64, logIdx uint32, logHash common.Hash) (nextIndex entrydb.EntryIdx, err error) + // Contains returns no error iff the specified logHash is recorded in the specified blockNum and logIdx. + // If the log is out of reach, then ErrFuture is returned. + // If the log is determined to conflict with the canonical chain, then ErrConflict is returned. + // logIdx is the index of the log in the array of all logs in the block. + // This can be used to check the validity of cross-chain interop events. + // The block-seal of the blockNum block, that the log was included in, is returned. + // This seal may be fully zeroed, without error, if the block isn't fully known yet. + Contains(blockNum uint64, logIdx uint32, logHash common.Hash) (includedIn types.BlockSeal, err error) + + // OpenBlock accumulates the ExecutingMessage events for a block and returns them + OpenBlock(blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) +} + +type LocalDerivedFromStorage interface { + First() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) + Latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) + AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error + LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error) + DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err error) + FirstAfter(derivedFrom, derived eth.BlockID) (nextDerivedFrom, nextDerived types.BlockSeal, err error) + NextDerivedFrom(derivedFrom eth.BlockID) (nextDerivedFrom types.BlockSeal, err error) + NextDerived(derived eth.BlockID) (derivedFrom types.BlockSeal, nextDerived types.BlockSeal, err error) + PreviousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error) + PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error) +} + +var _ LocalDerivedFromStorage = (*fromda.DB)(nil) + +type CrossDerivedFromStorage interface { + LocalDerivedFromStorage + // This will start to differ with reorg support } var _ LogStorage = (*logs.DB)(nil) -// ChainsDB is a database that stores logs and heads for multiple chains. -// it implements the ChainsStorage interface. +// ChainsDB is a database that stores logs and derived-from data for multiple chains. +// it implements the LogStorage interface, as well as several DB interfaces needed by the cross package. type ChainsDB struct { - logDBs map[types.ChainID]LogStorage - safetyIndex safety.SafetyIndex - logger log.Logger + // unsafe info: the sequence of block seals and events + logDBs locks.RWMap[types.ChainID, LogStorage] + + // cross-unsafe: how far we have processed the unsafe data. + // If present but set to a zeroed value the cross-unsafe will fallback to cross-safe. + crossUnsafe locks.RWMap[types.ChainID, *locks.RWValue[types.BlockSeal]] + + // local-safe: index of what we optimistically know about L2 blocks being derived from L1 + localDBs locks.RWMap[types.ChainID, LocalDerivedFromStorage] + + // cross-safe: index of L2 blocks we know to only have cross-L2 valid dependencies + crossDBs locks.RWMap[types.ChainID, CrossDerivedFromStorage] + + // finalized: the L1 finality progress. This can be translated into what may be considered as finalized in L2. + // It is initially zeroed, and the L2 finality query will return + // an error until it has this L1 finality to work with. + finalizedL1 locks.RWValue[eth.L1BlockRef] + + // depSet is the dependency set, used to determine what may be tracked, + // what is missing, and to provide it to DB users. + depSet depset.DependencySet + + logger log.Logger } -func NewChainsDB(logDBs map[types.ChainID]LogStorage, l log.Logger) *ChainsDB { - ret := &ChainsDB{ - logDBs: logDBs, +func NewChainsDB(l log.Logger, depSet depset.DependencySet) *ChainsDB { + return &ChainsDB{ logger: l, + depSet: depSet, } - ret.safetyIndex = safety.NewSafetyIndex(l, ret) - return ret } -func (db *ChainsDB) AddLogDB(chain types.ChainID, logDB LogStorage) { - if db.logDBs[chain] != nil { - log.Warn("overwriting existing logDB for chain", "chain", chain) +func (db *ChainsDB) AddLogDB(chainID types.ChainID, logDB LogStorage) { + if db.logDBs.Has(chainID) { + db.logger.Warn("overwriting existing log DB for chain", "chain", chainID) } - db.logDBs[chain] = logDB + + db.logDBs.Set(chainID, logDB) +} + +func (db *ChainsDB) AddLocalDerivedFromDB(chainID types.ChainID, dfDB LocalDerivedFromStorage) { + if db.localDBs.Has(chainID) { + db.logger.Warn("overwriting existing local derived-from DB for chain", "chain", chainID) + } + + db.localDBs.Set(chainID, dfDB) +} + +func (db *ChainsDB) AddCrossDerivedFromDB(chainID types.ChainID, dfDB CrossDerivedFromStorage) { + if db.crossDBs.Has(chainID) { + db.logger.Warn("overwriting existing cross derived-from DB for chain", "chain", chainID) + } + + db.crossDBs.Set(chainID, dfDB) } -func (db *ChainsDB) IteratorStartingAt(chain types.ChainID, sealedNum uint64, logIndex uint32) (logs.Iterator, error) { - logDB, ok := db.logDBs[chain] - if !ok { - return nil, fmt.Errorf("%w: %v", ErrUnknownChain, chain) +func (db *ChainsDB) AddCrossUnsafeTracker(chainID types.ChainID) { + if db.crossUnsafe.Has(chainID) { + db.logger.Warn("overwriting existing cross-unsafe tracker for chain", "chain", chainID) } - return logDB.IteratorStartingAt(sealedNum, logIndex) + db.crossUnsafe.Set(chainID, &locks.RWValue[types.BlockSeal]{}) } // ResumeFromLastSealedBlock prepares the chains db to resume recording events after a restart. // It rewinds the database to the last block that is guaranteed to have been fully recorded to the database, // to ensure it can resume recording from the first log of the next block. func (db *ChainsDB) ResumeFromLastSealedBlock() error { - for chain, logStore := range db.logDBs { + var result error + db.logDBs.Range(func(chain types.ChainID, logStore LogStorage) bool { headNum, ok := logStore.LatestSealedBlockNum() if !ok { // db must be empty, nothing to rewind to db.logger.Info("Resuming, but found no DB contents", "chain", chain) - continue + return true } db.logger.Info("Resuming, starting from last sealed block", "head", headNum) if err := logStore.Rewind(headNum); err != nil { - return fmt.Errorf("failed to rewind chain %s to sealed block %d", chain, headNum) - } - } - return nil -} - -// Check calls the underlying logDB to determine if the given log entry is safe with respect to the checker's criteria. -func (db *ChainsDB) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (common.Hash, error) { - logDB, ok := db.logDBs[chain] - if !ok { - return common.Hash{}, fmt.Errorf("%w: %v", ErrUnknownChain, chain) - } - _, err := logDB.Contains(blockNum, logIdx, logHash) - if err != nil { - return common.Hash{}, err - } - // TODO(#11693): need to get the actual block hash for this log entry for reorg detection - return common.Hash{}, nil -} - -// Safest returns the strongest safety level that can be guaranteed for the given log entry. -// it assumes the log entry has already been checked and is valid, this funcion only checks safety levels. -func (db *ChainsDB) Safest(chainID types.ChainID, blockNum uint64, index uint32) (safest types.SafetyLevel) { - safest = types.LocalUnsafe - if crossUnsafe, err := db.safetyIndex.CrossUnsafeL2(chainID); err == nil && crossUnsafe.WithinRange(blockNum, index) { - safest = types.CrossUnsafe - } - if localSafe, err := db.safetyIndex.LocalSafeL2(chainID); err == nil && localSafe.WithinRange(blockNum, index) { - safest = types.LocalSafe - } - if crossSafe, err := db.safetyIndex.LocalSafeL2(chainID); err == nil && crossSafe.WithinRange(blockNum, index) { - safest = types.CrossSafe - } - if finalized, err := db.safetyIndex.FinalizedL2(chainID); err == nil { - if finalized.Number >= blockNum { - safest = types.Finalized + result = fmt.Errorf("failed to rewind chain %s to sealed block %d", chain, headNum) + return false } - } - return -} - -func (db *ChainsDB) FindSealedBlock(chain types.ChainID, block eth.BlockID) (nextEntry entrydb.EntryIdx, err error) { - logDB, ok := db.logDBs[chain] - if !ok { - return 0, fmt.Errorf("%w: %v", ErrUnknownChain, chain) - } - return logDB.FindSealedBlock(block) + return true + }) + return result } -// LatestBlockNum returns the latest fully-sealed block number that has been recorded to the logs db -// for the given chain. It does not contain safety guarantees. -// The block number might not be available (empty database, or non-existent chain). -func (db *ChainsDB) LatestBlockNum(chain types.ChainID) (num uint64, ok bool) { - logDB, knownChain := db.logDBs[chain] - if !knownChain { - return 0, false - } - return logDB.LatestSealedBlockNum() -} - -func (db *ChainsDB) AddLog( - chain types.ChainID, - logHash common.Hash, - parentBlock eth.BlockID, - logIdx uint32, - execMsg *types.ExecutingMessage) error { - logDB, ok := db.logDBs[chain] - if !ok { - return fmt.Errorf("%w: %v", ErrUnknownChain, chain) - } - return logDB.AddLog(logHash, parentBlock, logIdx, execMsg) -} - -func (db *ChainsDB) SealBlock( - chain types.ChainID, - block eth.BlockRef) error { - logDB, ok := db.logDBs[chain] - if !ok { - return fmt.Errorf("%w: %v", ErrUnknownChain, chain) - } - err := logDB.SealBlock(block.ParentHash, block.ID(), block.Time) - if err != nil { - return fmt.Errorf("failed to seal block %v: %w", block, err) - } - err = db.safetyIndex.UpdateLocalUnsafe(chain, block) - if err != nil { - return fmt.Errorf("failed to update local-unsafe: %w", err) - } - return nil -} - -func (db *ChainsDB) Rewind(chain types.ChainID, headBlockNum uint64) error { - logDB, ok := db.logDBs[chain] - if !ok { - return fmt.Errorf("%w: %v", ErrUnknownChain, chain) - } - return logDB.Rewind(headBlockNum) +func (db *ChainsDB) DependencySet() depset.DependencySet { + return db.depSet } func (db *ChainsDB) Close() error { var combined error - for id, logDB := range db.logDBs { + db.logDBs.Range(func(id types.ChainID, logDB LogStorage) bool { if err := logDB.Close(); err != nil { combined = errors.Join(combined, fmt.Errorf("failed to close log db for chain %v: %w", id, err)) } - } + return true + }) return combined } diff --git a/op-supervisor/supervisor/backend/db/db_test.go b/op-supervisor/supervisor/backend/db/db_test.go deleted file mode 100644 index cfa9477ae6495..0000000000000 --- a/op-supervisor/supervisor/backend/db/db_test.go +++ /dev/null @@ -1,518 +0,0 @@ -package db - -/* -import ( - "errors" - "fmt" - "io" - "math/rand" // nosemgrep - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -func TestChainsDB_AddLog(t *testing.T) { - t.Run("UnknownChain", func(t *testing.T) { - db := NewChainsDB(nil, &stubHeadStorage{}, testlog.Logger(t, log.LevelDebug)) - err := db.AddLog(types.ChainIDFromUInt64(2), common.Hash{}, eth.BlockID{}, 33, nil) - require.ErrorIs(t, err, ErrUnknownChain) - }) - - t.Run("KnownChain", func(t *testing.T) { - chainID := types.ChainIDFromUInt64(1) - logDB := &stubLogDB{} - db := NewChainsDB(map[types.ChainID]LogStorage{ - chainID: logDB, - }, &stubHeadStorage{}, testlog.Logger(t, log.LevelDebug)) - bl10 := eth.BlockID{Hash: common.Hash{0x10}, Number: 10} - err := db.SealBlock(chainID, common.Hash{0x9}, bl10, 1234) - require.NoError(t, err, err) - err = db.AddLog(chainID, common.Hash{}, bl10, 0, nil) - require.NoError(t, err, err) - require.Equal(t, 1, logDB.addLogCalls) - require.Equal(t, 1, logDB.sealBlockCalls) - }) -} - -func TestChainsDB_Rewind(t *testing.T) { - t.Run("UnknownChain", func(t *testing.T) { - db := NewChainsDB(nil, &stubHeadStorage{}, testlog.Logger(t, log.LevelDebug)) - err := db.Rewind(types.ChainIDFromUInt64(2), 42) - require.ErrorIs(t, err, ErrUnknownChain) - }) - - t.Run("KnownChain", func(t *testing.T) { - chainID := types.ChainIDFromUInt64(1) - logDB := &stubLogDB{} - db := NewChainsDB(map[types.ChainID]LogStorage{ - chainID: logDB, - }, &stubHeadStorage{}, - testlog.Logger(t, log.LevelDebug)) - err := db.Rewind(chainID, 23) - require.NoError(t, err, err) - require.EqualValues(t, 23, logDB.headBlockNum) - }) -} - -func TestChainsDB_UpdateCrossHeads(t *testing.T) { - // using a chainID of 1 for simplicity - chainID := types.ChainIDFromUInt64(1) - // get default stubbed components - logDB, checker, h := setupStubbedForUpdateHeads(chainID) - - checker.numSafe = 1 - xSafe := checker.crossHeadForChain - - // The ChainsDB is real, but uses only stubbed components - db := NewChainsDB( - map[types.ChainID]LogStorage{ - chainID: logDB}, - &stubHeadStorage{h}, - testlog.Logger(t, log.LevelDebug)) - - err := db.UpdateCrossHeads(checker) - require.NoError(t, err) - // found a safe executing message, and no new initiating messages - require.Equal(t, xSafe+1, checker.updated) -} - -func TestChainsDB_UpdateCrossHeadsBeyondLocal(t *testing.T) { - // using a chainID of 1 for simplicity - chainID := types.ChainIDFromUInt64(1) - // get default stubbed components - logDB, checker, h := setupStubbedForUpdateHeads(chainID) - // set the safety checker to pass 99 times, effectively allowing all messages to be safe - checker.numSafe = 99 - - startLocalSafe := checker.localHeadForChain - - // The ChainsDB is real, but uses only stubbed components - db := NewChainsDB( - map[types.ChainID]LogStorage{ - chainID: logDB}, - &stubHeadStorage{h}, - testlog.Logger(t, log.LevelDebug)) - - // Update cross-heads is expected to: - // 1. get a last checkpoint iterator from the logDB (stubbed to be at 15) - // 2. progress the iterator to repeatedly, as the safety check will pass 99 times. - // 3. exceed the local head, and update the cross-head to the local head (40) - err := db.UpdateCrossHeads(checker) - require.NoError(t, err) - require.Equal(t, startLocalSafe, checker.updated) -} - -func TestChainsDB_UpdateCrossHeadsEOF(t *testing.T) { - // using a chainID of 1 for simplicity - chainID := types.ChainIDFromUInt64(1) - // get default stubbed components - logDB, checker, h := setupStubbedForUpdateHeads(chainID) - // set the log DB to return an EOF error when trying to get the next executing message - // after processing 10 message (with more messages available to be safe) - logDB.nextLogs = logDB.nextLogs[:checker.crossHeadForChain+11] - // This is a legacy test, the local head is further than the DB content... - - checker.numSafe = 99 - - // The ChainsDB is real, but uses only stubbed components - db := NewChainsDB( - map[types.ChainID]LogStorage{ - chainID: logDB}, - &stubHeadStorage{h}, - testlog.Logger(t, log.LevelDebug)) - - // Update cross-heads is expected to: - // - process 10 logs as safe, 5 of which execute something - // - update cross-safe to what was there - err := db.UpdateCrossHeads(checker) - require.NoError(t, err) - require.Equal(t, checker.crossHeadForChain+11, checker.updated) -} - -func TestChainsDB_UpdateCrossHeadsError(t *testing.T) { - // using a chainID of 1 for simplicity - chainID := types.ChainIDFromUInt64(1) - // get default stubbed components - logDB, checker, h := setupStubbedForUpdateHeads(chainID) - // set the log DB to return an error when trying to get the next executing message - // after processing 3 messages as safe (with more messages available to be safe) - - executed := 0 - for i, e := range logDB.nextLogs { - if executed == 3 { - logDB.nextLogs[i].err = errors.New("some error") - } - if entrydb.EntryIdx(i) > checker.crossHeadForChain && e.execIdx >= 0 { - executed++ - } - } - - // everything is safe until error - checker.numSafe = 99 - - // The ChainsDB is real, but uses only stubbed components - db := NewChainsDB( - map[types.ChainID]LogStorage{ - chainID: logDB}, - &stubHeadStorage{h}, - testlog.Logger(t, log.LevelDebug)) - - // Update cross-heads is expected to: - // 1. get a last checkpoint iterator from the logDB (stubbed to be at 10) - // 2. fail during execution, even after processing 3 messages as safe - // 3. exit without updating, returning the error - err := db.UpdateCrossHeads(checker) - require.Error(t, err) - // the update was never set (aka 0-value) - require.Equal(t, entrydb.EntryIdx(0), checker.updated) -} - -// setupStubbedForUpdateHeads sets up stubbed components for testing the UpdateCrossHeads method -// it returns stubbed structs which are suitable for their interfaces, and can be modified before testing -// TODO: the variables at the top of this function should be configurable by the caller. -// this isn't an issue for now, as all tests can modify the stubbed components directly after calling this function. -// but readability and maintainability would be improved by making this function more configurable. -func setupStubbedForUpdateHeads(chainID types.ChainID) (*stubLogDB, *stubChecker, *heads.Heads) { - // the last known cross-safe head is at 20 - cross := heads.HeadPointer{LastSealedBlockNum: 20} - // the local head (the limit of the update) is at 40 - local := heads.HeadPointer{LastSealedBlockNum: 40} - // the number of executing messages to make available (this should be more than the number of safety checks performed) - numExecutingMessages := 30 - // number of safety checks that will pass before returning false - numSafe := 1 - - // set up stubbed logDB - logDB := &stubLogDB{} - - // set up stubbed executing messages that the ChainsDB can pass to the checker - logDB.executingMessages = []*types.ExecutingMessage{} - for i := 0; i < numExecutingMessages; i++ { - // executing messages are packed in groups of 3, with block numbers increasing by 1 - logDB.executingMessages = append(logDB.executingMessages, &types.ExecutingMessage{ - BlockNum: uint64(100 + int(i/3)), - LogIdx: uint32(i), - Hash: common.Hash{}, - }) - } - - rng := rand.New(rand.NewSource(123)) - blockNum := uint64(100) - logIndex := uint32(0) - executedCount := 0 - for i := entrydb.EntryIdx(0); i <= local; i++ { - var logHash common.Hash - rng.Read(logHash[:]) - - execIndex := -1 - // All the even messages have an executing message - if i%2 == 0 { - execIndex = rng.Intn(len(logDB.executingMessages)) - executedCount += 1 - } - var msgErr error - - logDB.nextLogs = append(logDB.nextLogs, nextLogResponse{ - blockNum: blockNum, - logIdx: logIndex, - evtHash: logHash, - err: msgErr, - execIdx: execIndex, - }) - } - - // set up stubbed checker - checker := &stubChecker{ - localHeadForChain: local, - crossHeadForChain: cross, - // the first safety check will return true, the second false - numSafe: numSafe, - } - - // set up stubbed heads with sample values - h := heads.NewHeads() - h.Chains[chainID] = heads.ChainHeads{} - - return logDB, checker, h -} - -type stubChecker struct { - localHeadForChain heads.HeadPointer - crossHeadForChain heads.HeadPointer - numSafe int - checkCalls int - updated heads.HeadPointer -} - -func (s *stubChecker) String() string { - return "stubChecker" -} - -func (s *stubChecker) LocalSafetyLevel() types.SafetyLevel { - return types.Safe -} - -func (s *stubChecker) CrossSafetyLevel() types.SafetyLevel { - return types.Safe -} - -func (s *stubChecker) LocalHead(chainID types.ChainID) heads.HeadPointer { - return s.localHeadForChain -} - -func (s *stubChecker) CrossHead(chainID types.ChainID) heads.HeadPointer { - return s.crossHeadForChain -} - -// stubbed Check returns true for the first numSafe calls, and false thereafter -func (s *stubChecker) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) bool { - if s.checkCalls >= s.numSafe { - return fmt.Errorf("safety check failed") - } - s.checkCalls++ - return nil -} -func (s *stubChecker) CheckCross(chain types.ChainID, blockNum uint64, logIdx uint32, logHash backendTypes.TruncatedHash) error { - return s.check(chain, blockNum, logIdx, logHash) -} -func (s *stubChecker) CheckLocal(chain types.ChainID, blockNum uint64, logIdx uint32, logHash backendTypes.TruncatedHash) error { - return s.check(chain, blockNum, logIdx, logHash) -} - -func (s *stubChecker) Update(chain types.ChainID, h heads.HeadPointer) error { - s.updated = h - return nil -} -func (s *stubChecker) UpdateCross(chain types.ChainID, h heads.HeadPointer) error { - return s.Update(chain, h) -} -func (s *stubChecker) UpdateLocal(chain types.ChainID, h heads.HeadPointer) error { - return s.Update(chain, h) -} - -func (s *stubChecker) SafetyLevel() types.SafetyLevel { - return types.CrossSafe -} - -type stubHeadStorage struct { - heads *heads.Heads -} - -func (s *stubHeadStorage) UpdateLocalUnsafe(chainID types.ChainID, h heads.HeadPointer) error { - panic("not implemented") -} - -func (s *stubHeadStorage) UpdateLocalSafe(chainID types.ChainID, h heads.HeadPointer) error { - panic("not implemented") -} - -func (s *stubHeadStorage) UpdateLocalFinalized(chainID types.ChainID, h heads.HeadPointer) error { - panic("not implemented") -} - -func (s *stubHeadStorage) UpdateCrossUnsafe(chainID types.ChainID, h heads.HeadPointer) error { - panic("not implemented") -} - -func (s *stubHeadStorage) UpdateCrossSafe(chainID types.ChainID, h heads.HeadPointer) error { - panic("not implemented") -} - -func (s *stubHeadStorage) UpdateCrossFinalized(chainID types.ChainID, h heads.HeadPointer) error { - panic("not implemented") -} - -func (s *stubHeadStorage) LocalUnsafe(chainID types.ChainID) heads.HeadPointer { - panic("not implemented") -} - -func (s *stubHeadStorage) LocalSafe(chainID types.ChainID) heads.HeadPointer { - panic("not implemented") -} - -func (s *stubHeadStorage) LocalFinalized(chainID types.ChainID) heads.HeadPointer { - panic("not implemented") -} - -func (s *stubHeadStorage) CrossUnsafe(chainID types.ChainID) heads.HeadPointer { - panic("not implemented") -} - -func (s *stubHeadStorage) CrossSafe(chainID types.ChainID) heads.HeadPointer { - panic("not implemented") -} - -func (s *stubHeadStorage) CrossFinalized(chainID types.ChainID) heads.HeadPointer { - panic("not implemented") -} - -func (s *stubHeadStorage) Apply(heads.Operation) error { - return nil -} - -func (s *stubHeadStorage) Current() *heads.Heads { - if s.heads == nil { - s.heads = heads.NewHeads() - } - return s.heads.Copy() -} - -type nextLogResponse struct { - blockNum uint64 - - logIdx uint32 - - evtHash common.Hash - - err error - - // -1 if not executing - execIdx int -} - -type stubIterator struct { - index entrydb.EntryIdx - - db *stubLogDB -} - -func (s *stubIterator) End() error { - return nil // only used for DB-loading. The stub is already loaded -} - -func (s *stubIterator) NextInitMsg() error { - s.index += 1 - if s.index >= entrydb.EntryIdx(len(s.db.nextLogs)) { - return io.EOF - } - e := s.db.nextLogs[s.index] - return e.err -} - -func (s *stubIterator) NextExecMsg() error { - for { - s.index += 1 - if s.index >= entrydb.EntryIdx(len(s.db.nextLogs)) { - return io.EOF - } - e := s.db.nextLogs[s.index] - if e.err != nil { - return e.err - } - if e.execIdx >= 0 { - return nil - } - } -} - -func (s *stubIterator) NextBlock() error { - panic("not yet supported") -} - -func (s *stubIterator) NextIndex() entrydb.EntryIdx { - return s.index + 1 -} - -func (s *stubIterator) SealedBlock() (hash common.Hash, num uint64, ok bool) { - panic("not yet supported") -} - -func (s *stubIterator) InitMessage() (hash common.Hash, logIndex uint32, ok bool) { - if s.index < 0 { - return common.Hash{}, 0, false - } - if s.index >= entrydb.EntryIdx(len(s.db.nextLogs)) { - return common.Hash{}, 0, false - } - e := s.db.nextLogs[s.index] - return e.evtHash, e.logIdx, true -} - -func (s *stubIterator) ExecMessage() *types.ExecutingMessage { - if s.index < 0 { - return nil - } - if s.index >= entrydb.EntryIdx(len(s.db.nextLogs)) { - return nil - } - e := s.db.nextLogs[s.index] - if e.execIdx < 0 { - return nil - } - return s.db.executingMessages[e.execIdx] -} - -var _ logs.Iterator = (*stubIterator)(nil) - -type stubLogDB struct { - addLogCalls int - sealBlockCalls int - headBlockNum uint64 - - executingMessages []*types.ExecutingMessage - nextLogs []nextLogResponse - - containsResponse containsResponse -} - -func (s *stubLogDB) AddLog(logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *types.ExecutingMessage) error { - s.addLogCalls++ - return nil -} - -func (s *stubLogDB) SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error { - s.sealBlockCalls++ - return nil -} - -func (s *stubLogDB) LatestSealedBlockNum() (n uint64, ok bool) { - return s.headBlockNum, true -} - -func (s *stubLogDB) FindSealedBlock(block eth.BlockID) (nextEntry entrydb.EntryIdx, err error) { - panic("not implemented") -} - -func (s *stubLogDB) IteratorStartingAt(sealedNum uint64, logIndex uint32) (logs.Iterator, error) { - return &stubIterator{ - //index: i - 1, // TODO broken - db: s, - }, nil -} - -var _ LogStorage = (*stubLogDB)(nil) - -type containsResponse struct { - index entrydb.EntryIdx - err error -} - -// stubbed Contains records the arguments passed to it -// it returns the response set in the struct, or an empty response -func (s *stubLogDB) Contains(blockNum uint64, logIdx uint32, logHash common.Hash) (nextIndex entrydb.EntryIdx, err error) { - return s.containsResponse.index, s.containsResponse.err -} - -func (s *stubLogDB) Rewind(newHeadBlockNum uint64) error { - s.headBlockNum = newHeadBlockNum - return nil -} - -func (s *stubLogDB) LatestBlockNum() uint64 { - return s.headBlockNum -} - -func (s *stubLogDB) Close() error { - return nil -} -*/ diff --git a/op-supervisor/supervisor/backend/db/entrydb/entry_db.go b/op-supervisor/supervisor/backend/db/entrydb/entry_db.go index a260d143ddb59..210b2bafd0f44 100644 --- a/op-supervisor/supervisor/backend/db/entrydb/entry_db.go +++ b/op-supervisor/supervisor/backend/db/entrydb/entry_db.go @@ -9,71 +9,34 @@ import ( "github.com/ethereum/go-ethereum/log" ) -const ( - EntrySize = 34 -) - -type EntryIdx int64 - -type Entry [EntrySize]byte - -func (entry Entry) Type() EntryType { - return EntryType(entry[0]) +type EntryStore[T EntryType, E Entry[T]] interface { + Size() int64 + LastEntryIdx() EntryIdx + Read(idx EntryIdx) (E, error) + Append(entries ...E) error + Truncate(idx EntryIdx) error + Close() error } -type EntryTypeFlag uint8 - -const ( - FlagSearchCheckpoint EntryTypeFlag = 1 << TypeSearchCheckpoint - FlagCanonicalHash EntryTypeFlag = 1 << TypeCanonicalHash - FlagInitiatingEvent EntryTypeFlag = 1 << TypeInitiatingEvent - FlagExecutingLink EntryTypeFlag = 1 << TypeExecutingLink - FlagExecutingCheck EntryTypeFlag = 1 << TypeExecutingCheck - FlagPadding EntryTypeFlag = 1 << TypePadding - // for additional padding - FlagPadding2 EntryTypeFlag = FlagPadding << 1 -) - -func (ex EntryTypeFlag) Any(v EntryTypeFlag) bool { - return ex&v != 0 -} +type EntryIdx int64 -func (ex *EntryTypeFlag) Add(v EntryTypeFlag) { - *ex = *ex | v +type EntryType interface { + String() string + ~uint8 } -func (ex *EntryTypeFlag) Remove(v EntryTypeFlag) { - *ex = *ex &^ v +type Entry[T EntryType] interface { + Type() T + comparable } -type EntryType uint8 - -const ( - TypeSearchCheckpoint EntryType = iota - TypeCanonicalHash - TypeInitiatingEvent - TypeExecutingLink - TypeExecutingCheck - TypePadding -) - -func (d EntryType) String() string { - switch d { - case TypeSearchCheckpoint: - return "searchCheckpoint" - case TypeCanonicalHash: - return "canonicalHash" - case TypeInitiatingEvent: - return "initiatingEvent" - case TypeExecutingLink: - return "executingLink" - case TypeExecutingCheck: - return "executingCheck" - case TypePadding: - return "padding" - default: - return fmt.Sprintf("unknown-%d", uint8(d)) - } +// Binary is the binary interface to encode/decode/size entries. +// This should be a zero-cost abstraction, and is bundled as interface for the EntryDB +// to have generic access to this functionality without const-generics for array size in Go. +type Binary[T EntryType, E Entry[T]] interface { + Append(dest []byte, e *E) []byte + ReadAt(dest *E, r io.ReaderAt, at int64) (n int, err error) + EntrySize() int } // dataAccess defines a minimal API required to manipulate the actual stored data. @@ -85,10 +48,12 @@ type dataAccess interface { Truncate(size int64) error } -type EntryDB struct { +type EntryDB[T EntryType, E Entry[T], B Binary[T, E]] struct { data dataAccess lastEntryIdx EntryIdx + b B + cleanupFailedWrite bool } @@ -97,7 +62,7 @@ type EntryDB struct { // If the file exists it will be used as the existing data. // Returns ErrRecoveryRequired if the existing file is not a valid entry db. A EntryDB is still returned but all // operations will return ErrRecoveryRequired until the Recover method is called. -func NewEntryDB(logger log.Logger, path string) (*EntryDB, error) { +func NewEntryDB[T EntryType, E Entry[T], B Binary[T, E]](logger log.Logger, path string) (*EntryDB[T, E, B], error) { logger.Info("Opening entry database", "path", path) file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) if err != nil { @@ -107,13 +72,14 @@ func NewEntryDB(logger log.Logger, path string) (*EntryDB, error) { if err != nil { return nil, fmt.Errorf("failed to stat database at %v: %w", path, err) } - size := info.Size() / EntrySize - db := &EntryDB{ + var b B + size := info.Size() / int64(b.EntrySize()) + db := &EntryDB[T, E, B]{ data: file, lastEntryIdx: EntryIdx(size - 1), } - if size*EntrySize != info.Size() { - logger.Warn("File size is not a multiple of entry size. Truncating to last complete entry", "fileSize", size, "entrySize", EntrySize) + if size*int64(b.EntrySize()) != info.Size() { + logger.Warn("File size is not a multiple of entry size. Truncating to last complete entry", "fileSize", size, "entrySize", b.EntrySize()) if err := db.recover(); err != nil { return nil, fmt.Errorf("failed to recover database at %v: %w", path, err) } @@ -121,24 +87,26 @@ func NewEntryDB(logger log.Logger, path string) (*EntryDB, error) { return db, nil } -func (e *EntryDB) Size() int64 { +func (e *EntryDB[T, E, B]) Size() int64 { return int64(e.lastEntryIdx) + 1 } -func (e *EntryDB) LastEntryIdx() EntryIdx { +// LastEntryIdx returns the index of the last entry in the DB. +// This returns -1 if the DB is empty. +func (e *EntryDB[T, E, B]) LastEntryIdx() EntryIdx { return e.lastEntryIdx } // Read an entry from the database by index. Returns io.EOF iff idx is after the last entry. -func (e *EntryDB) Read(idx EntryIdx) (Entry, error) { +func (e *EntryDB[T, E, B]) Read(idx EntryIdx) (E, error) { + var out E if idx > e.lastEntryIdx { - return Entry{}, io.EOF + return out, io.EOF } - var out Entry - read, err := e.data.ReadAt(out[:], int64(idx)*EntrySize) + read, err := e.b.ReadAt(&out, e.data, int64(idx)*int64(e.b.EntrySize())) // Ignore io.EOF if we read the entire last entry as ReadAt may return io.EOF or nil when it reads the last byte - if err != nil && !(errors.Is(err, io.EOF) && read == EntrySize) { - return Entry{}, fmt.Errorf("failed to read entry %v: %w", idx, err) + if err != nil && !(errors.Is(err, io.EOF) && read == e.b.EntrySize()) { + return out, fmt.Errorf("failed to read entry %v: %w", idx, err) } return out, nil } @@ -147,16 +115,16 @@ func (e *EntryDB) Read(idx EntryIdx) (Entry, error) { // The entries are combined in memory and passed to a single Write invocation. // If the write fails, it will attempt to truncate any partially written data. // Subsequent writes to this instance will fail until partially written data is truncated. -func (e *EntryDB) Append(entries ...Entry) error { +func (e *EntryDB[T, E, B]) Append(entries ...E) error { if e.cleanupFailedWrite { // Try to rollback partially written data from a previous Append if truncateErr := e.Truncate(e.lastEntryIdx); truncateErr != nil { return fmt.Errorf("failed to recover from previous write error: %w", truncateErr) } } - data := make([]byte, 0, len(entries)*EntrySize) - for _, entry := range entries { - data = append(data, entry[:]...) + data := make([]byte, 0, len(entries)*e.b.EntrySize()) + for i := range entries { + data = e.b.Append(data, &entries[i]) } if n, err := e.data.Write(data); err != nil { if n == 0 { @@ -177,8 +145,8 @@ func (e *EntryDB) Append(entries ...Entry) error { } // Truncate the database so that the last retained entry is idx. Any entries after idx are deleted. -func (e *EntryDB) Truncate(idx EntryIdx) error { - if err := e.data.Truncate((int64(idx) + 1) * EntrySize); err != nil { +func (e *EntryDB[T, E, B]) Truncate(idx EntryIdx) error { + if err := e.data.Truncate((int64(idx) + 1) * int64(e.b.EntrySize())); err != nil { return fmt.Errorf("failed to truncate to entry %v: %w", idx, err) } // Update the lastEntryIdx cache @@ -188,13 +156,13 @@ func (e *EntryDB) Truncate(idx EntryIdx) error { } // recover an invalid database by truncating back to the last complete event. -func (e *EntryDB) recover() error { - if err := e.data.Truncate((e.Size()) * EntrySize); err != nil { +func (e *EntryDB[T, E, B]) recover() error { + if err := e.data.Truncate(e.Size() * int64(e.b.EntrySize())); err != nil { return fmt.Errorf("failed to truncate trailing partial entries: %w", err) } return nil } -func (e *EntryDB) Close() error { +func (e *EntryDB[T, E, B]) Close() error { return e.data.Close() } diff --git a/op-supervisor/supervisor/backend/db/entrydb/entry_db_test.go b/op-supervisor/supervisor/backend/db/entrydb/entry_db_test.go index bc9a871bea26e..7d6a687cf6e8d 100644 --- a/op-supervisor/supervisor/backend/db/entrydb/entry_db_test.go +++ b/op-supervisor/supervisor/backend/db/entrydb/entry_db_test.go @@ -3,16 +3,51 @@ package entrydb import ( "bytes" "errors" + "fmt" "io" "os" "path/filepath" "testing" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" ) +type TestEntryType uint8 + +func (typ TestEntryType) String() string { + return fmt.Sprintf("%d", uint8(typ)) +} + +const TestEntrySize = 34 + +type TestEntry [TestEntrySize]byte + +func (t TestEntry) Type() TestEntryType { + return TestEntryType(t[0]) +} + +type TestEntryBinary struct{} + +func (TestEntryBinary) Append(dest []byte, e *TestEntry) []byte { + return append(dest, e[:]...) +} + +func (TestEntryBinary) ReadAt(dest *TestEntry, r io.ReaderAt, at int64) (n int, err error) { + return r.ReadAt(dest[:], at) +} + +func (TestEntryBinary) EntrySize() int { + return TestEntrySize +} + +var _ Binary[TestEntryType, TestEntry] = TestEntryBinary{} + +type TestEntryDB = EntryDB[TestEntryType, TestEntry, TestEntryBinary] + func TestReadWrite(t *testing.T) { t.Run("BasicReadWrite", func(t *testing.T) { db := createEntryDB(t) @@ -111,10 +146,10 @@ func TestTruncateTrailingPartialEntries(t *testing.T) { entry2 := createEntry(2) invalidData := make([]byte, len(entry1)+len(entry2)+4) copy(invalidData, entry1[:]) - copy(invalidData[EntrySize:], entry2[:]) + copy(invalidData[TestEntrySize:], entry2[:]) invalidData[len(invalidData)-1] = 3 // Some invalid trailing data require.NoError(t, os.WriteFile(file, invalidData, 0o644)) - db, err := NewEntryDB(logger, file) + db, err := NewEntryDB[TestEntryType, TestEntry, TestEntryBinary](logger, file) require.NoError(t, err) defer db.Close() @@ -122,7 +157,7 @@ func TestTruncateTrailingPartialEntries(t *testing.T) { require.EqualValues(t, 2, db.Size()) stat, err := os.Stat(file) require.NoError(t, err) - require.EqualValues(t, 2*EntrySize, stat.Size()) + require.EqualValues(t, 2*TestEntrySize, stat.Size()) } func TestWriteErrors(t *testing.T) { @@ -153,7 +188,7 @@ func TestWriteErrors(t *testing.T) { t.Run("PartialWriteAndTruncateFails", func(t *testing.T) { db, stubData := createEntryDBWithStubData() stubData.writeErr = expectedErr - stubData.writeErrAfterBytes = EntrySize + 2 + stubData.writeErrAfterBytes = TestEntrySize + 2 stubData.truncateErr = errors.New("boom") err := db.Append(createEntry(1), createEntry(2)) require.ErrorIs(t, err, expectedErr) @@ -177,19 +212,19 @@ func TestWriteErrors(t *testing.T) { }) } -func requireRead(t *testing.T, db *EntryDB, idx EntryIdx, expected Entry) { +func requireRead(t *testing.T, db *TestEntryDB, idx EntryIdx, expected TestEntry) { actual, err := db.Read(idx) require.NoError(t, err) require.Equal(t, expected, actual) } -func createEntry(i byte) Entry { - return Entry(bytes.Repeat([]byte{i}, EntrySize)) +func createEntry(i byte) TestEntry { + return TestEntry(bytes.Repeat([]byte{i}, TestEntrySize)) } -func createEntryDB(t *testing.T) *EntryDB { +func createEntryDB(t *testing.T) *TestEntryDB { logger := testlog.Logger(t, log.LvlInfo) - db, err := NewEntryDB(logger, filepath.Join(t.TempDir(), "entries.db")) + db, err := NewEntryDB[TestEntryType, TestEntry, TestEntryBinary](logger, filepath.Join(t.TempDir(), "entries.db")) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, db.Close()) @@ -197,9 +232,9 @@ func createEntryDB(t *testing.T) *EntryDB { return db } -func createEntryDBWithStubData() (*EntryDB, *stubDataAccess) { +func createEntryDBWithStubData() (*TestEntryDB, *stubDataAccess) { stubData := &stubDataAccess{} - db := &EntryDB{data: stubData, lastEntryIdx: -1} + db := &EntryDB[TestEntryType, TestEntry, TestEntryBinary]{data: stubData, lastEntryIdx: -1} return db, stubData } diff --git a/op-supervisor/supervisor/backend/db/entrydb/memdb.go b/op-supervisor/supervisor/backend/db/entrydb/memdb.go new file mode 100644 index 0000000000000..6fb3eab62d5aa --- /dev/null +++ b/op-supervisor/supervisor/backend/db/entrydb/memdb.go @@ -0,0 +1,39 @@ +package entrydb + +import ( + "io" +) + +type MemEntryStore[T EntryType, E Entry[T]] struct { + entries []E +} + +func (s *MemEntryStore[T, E]) Size() int64 { + return int64(len(s.entries)) +} + +func (s *MemEntryStore[T, E]) LastEntryIdx() EntryIdx { + return EntryIdx(s.Size() - 1) +} + +func (s *MemEntryStore[T, E]) Read(idx EntryIdx) (E, error) { + if idx < EntryIdx(len(s.entries)) { + return s.entries[idx], nil + } + var out E + return out, io.EOF +} + +func (s *MemEntryStore[T, E]) Append(entries ...E) error { + s.entries = append(s.entries, entries...) + return nil +} + +func (s *MemEntryStore[T, E]) Truncate(idx EntryIdx) error { + s.entries = s.entries[:min(s.Size()-1, int64(idx+1))] + return nil +} + +func (s *MemEntryStore[T, E]) Close() error { + return nil +} diff --git a/op-supervisor/supervisor/backend/file_layout.go b/op-supervisor/supervisor/backend/db/file_layout.go similarity index 59% rename from op-supervisor/supervisor/backend/file_layout.go rename to op-supervisor/supervisor/backend/db/file_layout.go index 2a94266ef6053..65a2823170dc4 100644 --- a/op-supervisor/supervisor/backend/file_layout.go +++ b/op-supervisor/supervisor/backend/db/file_layout.go @@ -1,4 +1,4 @@ -package backend +package db import ( "fmt" @@ -8,6 +8,22 @@ import ( "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) +func prepLocalDerivedFromDBPath(chainID types.ChainID, datadir string) (string, error) { + dir, err := prepChainDir(chainID, datadir) + if err != nil { + return "", err + } + return filepath.Join(dir, "local_safe.db"), nil +} + +func prepCrossDerivedFromDBPath(chainID types.ChainID, datadir string) (string, error) { + dir, err := prepChainDir(chainID, datadir) + if err != nil { + return "", err + } + return filepath.Join(dir, "cross_safe.db"), nil +} + func prepLogDBPath(chainID types.ChainID, datadir string) (string, error) { dir, err := prepChainDir(chainID, datadir) if err != nil { @@ -24,7 +40,7 @@ func prepChainDir(chainID types.ChainID, datadir string) (string, error) { return dir, nil } -func prepDataDir(datadir string) error { +func PrepDataDir(datadir string) error { if err := os.MkdirAll(datadir, 0755); err != nil { return fmt.Errorf("failed to create data directory %v: %w", datadir, err) } diff --git a/op-supervisor/supervisor/backend/file_layout_test.go b/op-supervisor/supervisor/backend/db/file_layout_test.go similarity index 98% rename from op-supervisor/supervisor/backend/file_layout_test.go rename to op-supervisor/supervisor/backend/db/file_layout_test.go index ae06c3cd6ea46..ab41448805678 100644 --- a/op-supervisor/supervisor/backend/file_layout_test.go +++ b/op-supervisor/supervisor/backend/db/file_layout_test.go @@ -1,4 +1,4 @@ -package backend +package db import ( "math/big" diff --git a/op-supervisor/supervisor/backend/db/fromda/db.go b/op-supervisor/supervisor/backend/db/fromda/db.go new file mode 100644 index 0000000000000..48d8564ced202 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/db.go @@ -0,0 +1,349 @@ +package fromda + +import ( + "cmp" + "fmt" + "io" + "sort" + "sync" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type EntryStore interface { + Size() int64 + LastEntryIdx() entrydb.EntryIdx + Read(idx entrydb.EntryIdx) (Entry, error) + Append(entries ...Entry) error + Truncate(idx entrydb.EntryIdx) error + Close() error +} + +// DB implements an append only database for log data and cross-chain dependencies. +// Each entry is fixed size, and denotes an increment in L1 (derived-from) and/or L2 (derived) block. +// Data is an append-only log, that can be binary searched for any necessary derivation-link data. +type DB struct { + log log.Logger + m Metrics + store EntryStore + rwLock sync.RWMutex +} + +func NewFromFile(logger log.Logger, m Metrics, path string) (*DB, error) { + store, err := entrydb.NewEntryDB[EntryType, Entry, EntryBinary](logger, path) + if err != nil { + return nil, fmt.Errorf("failed to open DB: %w", err) + } + return NewFromEntryStore(logger, m, store) +} + +func NewFromEntryStore(logger log.Logger, m Metrics, store EntryStore) (*DB, error) { + db := &DB{ + log: logger, + m: m, + store: store, + } + db.m.RecordDBDerivedEntryCount(db.store.Size()) + return db, nil +} + +// Rewind to the last entry that was derived from a L1 block with the given block number. +func (db *DB) Rewind(derivedFrom uint64) error { + index, _, err := db.lastDerivedAt(derivedFrom) + if err != nil { + return fmt.Errorf("failed to find point to rewind to: %w", err) + } + err = db.store.Truncate(index) + if err != nil { + return err + } + db.m.RecordDBDerivedEntryCount(int64(index) + 1) + return nil +} + +// First returns the first known values, alike to Latest. +func (db *DB) First() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + lastIndex := db.store.LastEntryIdx() + if lastIndex < 0 { + return types.BlockSeal{}, types.BlockSeal{}, types.ErrFuture + } + last, err := db.readAt(0) + if err != nil { + return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("failed to read first derivation data: %w", err) + } + return last.derivedFrom, last.derived, nil +} + +func (db *DB) PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + // get the last time this L2 block was seen. + selfIndex, self, err := db.firstDerivedFrom(derived.Number) + if err != nil { + return types.BlockSeal{}, fmt.Errorf("failed to find derived %d: %w", derived.Number, err) + } + if self.derived.ID() != derived { + return types.BlockSeal{}, fmt.Errorf("found %s, but expected %s: %w", self.derived, derived, types.ErrConflict) + } + if selfIndex == 0 { // genesis block has a zeroed block as parent block + return types.BlockSeal{}, nil + } + prev, err := db.readAt(selfIndex - 1) + if err != nil { + return types.BlockSeal{}, fmt.Errorf("cannot find previous derived before %s: %w", derived, err) + } + return prev.derived, nil +} + +// Latest returns the last known values: +// derivedFrom: the L1 block that the L2 block is safe for (not necessarily the first, multiple L2 blocks may be derived from the same L1 block). +// derived: the L2 block that was derived (not necessarily the first, the L1 block may have been empty and repeated the last safe L2 block). +func (db *DB) Latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + return db.latest() +} + +// latest is like Latest, but without lock, for internal use. +func (db *DB) latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { + lastIndex := db.store.LastEntryIdx() + if lastIndex < 0 { + return types.BlockSeal{}, types.BlockSeal{}, types.ErrFuture + } + last, err := db.readAt(lastIndex) + if err != nil { + return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("failed to read last derivation data: %w", err) + } + return last.derivedFrom, last.derived, nil +} + +// LastDerivedAt returns the last L2 block derived from the given L1 block. +func (db *DB) LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + _, link, err := db.lastDerivedAt(derivedFrom.Number) + if err != nil { + return types.BlockSeal{}, err + } + if link.derivedFrom.ID() != derivedFrom { + return types.BlockSeal{}, fmt.Errorf("searched for last derived-from %s but found %s: %w", + derivedFrom, link.derivedFrom, types.ErrConflict) + } + return link.derived, nil +} + +// NextDerived finds the next L2 block after derived, and what it was derived from +func (db *DB) NextDerived(derived eth.BlockID) (derivedFrom types.BlockSeal, nextDerived types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + // get the last time this L2 block was seen. + selfIndex, self, err := db.lastDerivedFrom(derived.Number) + if err != nil { + return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("failed to find derived %d: %w", derived.Number, err) + } + if self.derived.ID() != derived { + return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("found %s, but expected %s: %w", self.derived, derived, types.ErrConflict) + } + next, err := db.readAt(selfIndex + 1) + if err != nil { + return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("cannot find next derived after %s: %w", derived, err) + } + return next.derivedFrom, next.derived, nil +} + +// DerivedFrom determines where a L2 block was first derived from. +// (a L2 block may repeat if the following L1 blocks are empty and don't produce additional L2 blocks) +func (db *DB) DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + _, link, err := db.firstDerivedFrom(derived.Number) + if err != nil { + return types.BlockSeal{}, err + } + if link.derived.ID() != derived { + return types.BlockSeal{}, fmt.Errorf("searched for first derived %s but found %s: %w", + derived, link.derived, types.ErrConflict) + } + return link.derivedFrom, nil +} + +func (db *DB) PreviousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + // get the last time this L1 block was seen. + selfIndex, self, err := db.firstDerivedAt(derivedFrom.Number) + if err != nil { + return types.BlockSeal{}, fmt.Errorf("failed to find derived %d: %w", derivedFrom.Number, err) + } + if self.derivedFrom.ID() != derivedFrom { + return types.BlockSeal{}, fmt.Errorf("found %s, but expected %s: %w", self.derivedFrom, derivedFrom, types.ErrConflict) + } + if selfIndex == 0 { + // genesis block has a zeroed block as parent block + if self.derivedFrom.Number == 0 { + return types.BlockSeal{}, nil + } else { + return types.BlockSeal{}, + fmt.Errorf("cannot find previous derived before start of database: %s (%w)", derivedFrom, types.ErrPreviousToFirst) + } + } + prev, err := db.readAt(selfIndex - 1) + if err != nil { + return types.BlockSeal{}, fmt.Errorf("cannot find previous derived before %s: %w", derivedFrom, err) + } + return prev.derivedFrom, nil +} + +// NextDerivedFrom finds the next L1 block after derivedFrom +func (db *DB) NextDerivedFrom(derivedFrom eth.BlockID) (nextDerivedFrom types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + selfIndex, self, err := db.lastDerivedAt(derivedFrom.Number) + if err != nil { + return types.BlockSeal{}, fmt.Errorf("failed to find derived-from %d: %w", derivedFrom.Number, err) + } + if self.derivedFrom.ID() != derivedFrom { + return types.BlockSeal{}, fmt.Errorf("found %s, but expected %s: %w", self.derivedFrom, derivedFrom, types.ErrConflict) + } + next, err := db.readAt(selfIndex + 1) + if err != nil { + return types.BlockSeal{}, fmt.Errorf("cannot find next derived-from after %s: %w", derivedFrom, err) + } + return next.derivedFrom, nil +} + +// FirstAfter determines the next entry after the given pair of derivedFrom, derived. +// Either one or both of the two entries will be an increment by 1 +func (db *DB) FirstAfter(derivedFrom, derived eth.BlockID) (nextDerivedFrom, nextDerived types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + selfIndex, selfLink, err := db.lookup(derivedFrom.Number, derived.Number) + if err != nil { + return types.BlockSeal{}, types.BlockSeal{}, err + } + if selfLink.derivedFrom.ID() != derivedFrom { + return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("DB has derived-from %s but expected %s: %w", selfLink.derivedFrom, derivedFrom, types.ErrConflict) + } + if selfLink.derived.ID() != derived { + return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("DB has derived %s but expected %s: %w", selfLink.derived, derived, types.ErrConflict) + } + next, err := db.readAt(selfIndex + 1) + if err != nil { + return types.BlockSeal{}, types.BlockSeal{}, err + } + return next.derivedFrom, next.derived, nil +} + +func (db *DB) lastDerivedFrom(derived uint64) (entrydb.EntryIdx, LinkEntry, error) { + return db.find(true, func(link LinkEntry) int { + return cmp.Compare(derived, link.derived.Number) + }) +} + +func (db *DB) firstDerivedFrom(derived uint64) (entrydb.EntryIdx, LinkEntry, error) { + return db.find(false, func(link LinkEntry) int { + return cmp.Compare(link.derived.Number, derived) + }) +} + +func (db *DB) lookup(derivedFrom, derived uint64) (entrydb.EntryIdx, LinkEntry, error) { + return db.find(false, func(link LinkEntry) int { + res := cmp.Compare(link.derived.Number, derived) + if res == 0 { + return cmp.Compare(link.derivedFrom.Number, derivedFrom) + } + return res + }) +} + +func (db *DB) lastDerivedAt(derivedFrom uint64) (entrydb.EntryIdx, LinkEntry, error) { + // Reverse: prioritize the last entry. + return db.find(true, func(link LinkEntry) int { + return cmp.Compare(derivedFrom, link.derivedFrom.Number) + }) +} + +func (db *DB) firstDerivedAt(derivedFrom uint64) (entrydb.EntryIdx, LinkEntry, error) { + return db.find(false, func(link LinkEntry) int { + return cmp.Compare(link.derivedFrom.Number, derivedFrom) + }) +} + +// find finds the first entry for which cmpFn(link) returns 0. +// The cmpFn entries to the left should return -1, entries to the right 1. +// If reverse, the cmpFn should be flipped too, and the last entry for which cmpFn(link) is 0 will be found. +func (db *DB) find(reverse bool, cmpFn func(link LinkEntry) int) (entrydb.EntryIdx, LinkEntry, error) { + n := db.store.Size() + if n == 0 { + return -1, LinkEntry{}, types.ErrFuture + } + var searchErr error + // binary-search for the smallest index i for which cmp(i) >= 0 + result := sort.Search(int(n), func(i int) bool { + at := entrydb.EntryIdx(i) + if reverse { + at = entrydb.EntryIdx(n) - 1 - at + } + entry, err := db.readAt(at) + if err != nil { + searchErr = err + return false + } + return cmpFn(entry) >= 0 + }) + if searchErr != nil { + return -1, LinkEntry{}, fmt.Errorf("failed to search: %w", searchErr) + } + if result == int(n) { + if reverse { + return -1, LinkEntry{}, fmt.Errorf("no entry found: %w", types.ErrSkipped) + } else { + return -1, LinkEntry{}, fmt.Errorf("no entry found: %w", types.ErrFuture) + } + } + if reverse { + result = int(n) - 1 - result + } + link, err := db.readAt(entrydb.EntryIdx(result)) + if err != nil { + return -1, LinkEntry{}, fmt.Errorf("failed to read final result entry %d: %w", result, err) + } + if cmpFn(link) != 0 { + if reverse { + return -1, LinkEntry{}, fmt.Errorf("lowest entry %s is too high: %w", link, types.ErrFuture) + } else { + return -1, LinkEntry{}, fmt.Errorf("lowest entry %s is too high: %w", link, types.ErrSkipped) + } + } + if cmpFn(link) != 0 { + // Search should have returned lowest entry >= the target. + // And we already checked it's not > the target + panic(fmt.Errorf("invalid search result %s, did not match equality check", link)) + } + return entrydb.EntryIdx(result), link, nil +} + +func (db *DB) readAt(i entrydb.EntryIdx) (LinkEntry, error) { + entry, err := db.store.Read(i) + if err != nil { + if err == io.EOF { + return LinkEntry{}, types.ErrFuture + } + return LinkEntry{}, err + } + var out LinkEntry + err = out.decode(entry) + return out, err +} + +func (db *DB) Close() error { + db.rwLock.Lock() + defer db.rwLock.Unlock() + return db.store.Close() +} diff --git a/op-supervisor/supervisor/backend/db/fromda/db_invariants_test.go b/op-supervisor/supervisor/backend/db/fromda/db_invariants_test.go new file mode 100644 index 0000000000000..abde003c24592 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/db_invariants_test.go @@ -0,0 +1,118 @@ +package fromda + +import ( + "fmt" + "io" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +type statInvariant func(stat os.FileInfo, m *stubMetrics) error +type linkInvariant func(prev, current LinkEntry) error + +// checkDBInvariants reads the database log directly and asserts a set of invariants on the data. +func checkDBInvariants(t *testing.T, dbPath string, m *stubMetrics) { + stat, err := os.Stat(dbPath) + require.NoError(t, err) + + statInvariants := []statInvariant{ + invariantFileSizeMultipleOfEntrySize, + invariantFileSizeMatchesEntryCountMetric, + } + for _, invariant := range statInvariants { + require.NoError(t, invariant(stat, m)) + } + + // Read all entries as binary blobs + file, err := os.OpenFile(dbPath, os.O_RDONLY, 0o644) + require.NoError(t, err) + entries := make([]Entry, stat.Size()/EntrySize) + for i := range entries { + n, err := io.ReadFull(file, entries[i][:]) + require.NoErrorf(t, err, "failed to read entry %v", i) + require.EqualValuesf(t, EntrySize, n, "read wrong length for entry %v", i) + } + var links []LinkEntry + for i, e := range entries { + var v LinkEntry + require.NoError(t, v.decode(e), "failed to decode entry %d", i) + links = append(links, v) + } + + linkInvariants := []linkInvariant{ + invariantDerivedTimestamp, + invariantDerivedFromTimestamp, + invariantNumberIncrement, + } + for i, link := range links { + if i == 0 { + continue + } + for _, invariant := range linkInvariants { + err := invariant(links[i-1], link) + if err != nil { + require.NoErrorf(t, err, "Invariant breached: \n%v", fmtEntries(entries)) + } + } + } +} + +func fmtEntries(entries []Entry) string { + out := "" + for i, entry := range entries { + out += fmt.Sprintf("%v: %x\n", i, entry) + } + return out +} + +func invariantFileSizeMultipleOfEntrySize(stat os.FileInfo, _ *stubMetrics) error { + size := stat.Size() + if size%EntrySize != 0 { + return fmt.Errorf("expected file size to be a multiple of entry size (%v) but was %v", EntrySize, size) + } + return nil +} + +func invariantFileSizeMatchesEntryCountMetric(stat os.FileInfo, m *stubMetrics) error { + size := stat.Size() + if m.DBDerivedEntryCount*EntrySize != size { + return fmt.Errorf("expected file size to be entryCount (%v) * entrySize (%v) = %v but was %v", m.DBDerivedEntryCount, EntrySize, m.DBDerivedEntryCount*EntrySize, size) + } + return nil +} + +func invariantDerivedTimestamp(prev, current LinkEntry) error { + if current.derived.Timestamp < prev.derived.Timestamp { + return fmt.Errorf("derived timestamp must be >=, current: %s, prev: %s", current.derived, prev.derived) + } + return nil +} + +func invariantNumberIncrement(prev, current LinkEntry) error { + // derived stays the same if the new L1 block is empty. + derivedSame := current.derived.Number == prev.derived.Number + // derivedFrom stays the same if this L2 block is derived from the same L1 block as the last L2 block + derivedFromSame := current.derivedFrom.Number == prev.derivedFrom.Number + // At least one of the two must increment, otherwise we are just repeating data in the DB. + if derivedSame && derivedFromSame { + return fmt.Errorf("expected at least either derivedFrom or derived to increment, but both have same number") + } + derivedIncrement := current.derived.Number == prev.derived.Number+1 + derivedFromIncrement := current.derivedFrom.Number == prev.derivedFrom.Number+1 + if !(derivedSame || derivedIncrement) { + return fmt.Errorf("expected derived to either stay the same or increment, got prev %s current %s", prev.derived, current.derived) + } + if !(derivedFromSame || derivedFromIncrement) { + return fmt.Errorf("expected derivedFrom to either stay the same or increment, got prev %s current %s", prev.derivedFrom, current.derivedFrom) + } + return nil +} + +func invariantDerivedFromTimestamp(prev, current LinkEntry) error { + if current.derivedFrom.Timestamp < prev.derivedFrom.Timestamp { + return fmt.Errorf("derivedFrom timestamp must be >=, current: %s, prev: %s", current.derivedFrom, prev.derivedFrom) + } + return nil +} diff --git a/op-supervisor/supervisor/backend/db/fromda/db_test.go b/op-supervisor/supervisor/backend/db/fromda/db_test.go new file mode 100644 index 0000000000000..8d6a273e1f986 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/db_test.go @@ -0,0 +1,714 @@ +package fromda + +import ( + "fmt" + "io/fs" + "math/rand" // nosemgrep + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type stubMetrics struct { + DBDerivedEntryCount int64 +} + +func (s *stubMetrics) RecordDBDerivedEntryCount(count int64) { + s.DBDerivedEntryCount = count +} + +var _ Metrics = (*stubMetrics)(nil) + +type setupFn func(t *testing.T, db *DB, m *stubMetrics) + +type assertFn func(t *testing.T, db *DB, m *stubMetrics) + +func runDBTest(t *testing.T, setup setupFn, assert assertFn) { + createDb := func(t *testing.T, dir string) (*DB, *stubMetrics, string) { + logger := testlog.Logger(t, log.LvlTrace) + path := filepath.Join(dir, "test.db") + m := &stubMetrics{} + db, err := NewFromFile(logger, m, path) + require.NoError(t, err, "Failed to create database") + t.Cleanup(func() { + err := db.Close() + if err != nil { + require.ErrorIs(t, err, fs.ErrClosed) + } + }) + return db, m, path + } + + t.Run("New", func(t *testing.T) { + db, m, _ := createDb(t, t.TempDir()) + setup(t, db, m) + assert(t, db, m) + }) + + t.Run("Existing", func(t *testing.T) { + dir := t.TempDir() + db, m, path := createDb(t, dir) + setup(t, db, m) + // Close and recreate the database + require.NoError(t, db.Close()) + checkDBInvariants(t, path, m) + + db2, m, path := createDb(t, dir) + assert(t, db2, m) + checkDBInvariants(t, path, m) + }) +} + +func TestEmptyDB(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) {}, + func(t *testing.T, db *DB, m *stubMetrics) { + _, _, err := db.Latest() + require.ErrorIs(t, err, types.ErrFuture) + + _, _, err = db.First() + require.ErrorIs(t, err, types.ErrFuture) + + _, err = db.LastDerivedAt(eth.BlockID{}) + require.ErrorIs(t, err, types.ErrFuture) + + _, err = db.DerivedFrom(eth.BlockID{}) + require.ErrorIs(t, err, types.ErrFuture) + + _, err = db.PreviousDerived(eth.BlockID{}) + require.ErrorIs(t, err, types.ErrFuture) + + _, _, err = db.NextDerived(eth.BlockID{}) + require.ErrorIs(t, err, types.ErrFuture) + + _, err = db.PreviousDerivedFrom(eth.BlockID{}) + require.ErrorIs(t, err, types.ErrFuture) + + _, err = db.NextDerivedFrom(eth.BlockID{}) + require.ErrorIs(t, err, types.ErrFuture) + + _, _, err = db.FirstAfter(eth.BlockID{}, eth.BlockID{}) + require.ErrorIs(t, err, types.ErrFuture) + }) +} + +func mockL1(i uint64) types.BlockSeal { + return types.BlockSeal{ + Hash: crypto.Keccak256Hash([]byte(fmt.Sprintf("L1 block %d", i))), + Number: i, + Timestamp: 1000_000 + i*12, + } +} + +func mockL2(i uint64) types.BlockSeal { + var h common.Hash + if i != 0 { + h = crypto.Keccak256Hash([]byte(fmt.Sprintf("L1 block %d", i))) + } + return types.BlockSeal{ + Hash: h, + Number: i, + Timestamp: 1000_000 + i*12, + } +} + +func toRef(seal types.BlockSeal, parentHash common.Hash) eth.BlockRef { + return eth.BlockRef{ + Hash: seal.Hash, + Number: seal.Number, + ParentHash: parentHash, + Time: seal.Timestamp, + } +} + +func TestSingleEntryDB(t *testing.T) { + expectedDerivedFrom := mockL1(0) + expectedDerived := mockL2(2) + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddDerived(toRef(expectedDerivedFrom, mockL1(0).Hash), toRef(expectedDerived, mockL2(0).Hash))) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + // First + derivedFrom, derived, err := db.First() + require.NoError(t, err) + require.Equal(t, expectedDerivedFrom, derivedFrom) + require.Equal(t, expectedDerived, derived) + + // Latest + derivedFrom, derived, err = db.Latest() + require.NoError(t, err) + require.Equal(t, expectedDerivedFrom, derivedFrom) + require.Equal(t, expectedDerived, derived) + + // FirstAfter Latest + _, _, err = db.FirstAfter(derivedFrom.ID(), derived.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + // LastDerivedAt + derived, err = db.LastDerivedAt(expectedDerivedFrom.ID()) + require.NoError(t, err) + require.Equal(t, expectedDerived, derived) + + // LastDerivedAt with a non-existent block + _, err = db.LastDerivedAt(eth.BlockID{Hash: common.Hash{0xaa}, Number: expectedDerivedFrom.Number}) + require.ErrorIs(t, err, types.ErrConflict) + + // FirstAfter with a non-existent block (derived and derivedFrom) + _, _, err = db.FirstAfter(eth.BlockID{Hash: common.Hash{0xaa}, Number: expectedDerivedFrom.Number}, expectedDerived.ID()) + require.ErrorIs(t, err, types.ErrConflict) + _, _, err = db.FirstAfter(expectedDerivedFrom.ID(), eth.BlockID{Hash: common.Hash{0xaa}, Number: expectedDerived.Number}) + require.ErrorIs(t, err, types.ErrConflict) + + // DerivedFrom + derivedFrom, err = db.DerivedFrom(expectedDerived.ID()) + require.NoError(t, err) + require.Equal(t, expectedDerivedFrom, derivedFrom) + + // DerivedFrom with a non-existent block + _, err = db.DerivedFrom(eth.BlockID{Hash: common.Hash{0xbb}, Number: expectedDerived.Number}) + require.ErrorIs(t, err, types.ErrConflict) + + // PreviousDerived + prev, err := db.PreviousDerived(expectedDerived.ID()) + require.NoError(t, err) + require.Equal(t, types.BlockSeal{}, prev, "zeroed seal before first entry") + + // PreviousDerivedFrom + prev, err = db.PreviousDerivedFrom(expectedDerivedFrom.ID()) + require.NoError(t, err) + require.Equal(t, types.BlockSeal{}, prev, "zeroed seal before first entry") + + // NextDerived + _, _, err = db.NextDerived(expectedDerived.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + // NextDerivedFrom + _, err = db.NextDerivedFrom(expectedDerivedFrom.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + // FirstAfter + _, _, err = db.FirstAfter(expectedDerivedFrom.ID(), expectedDerived.ID()) + require.ErrorIs(t, err, types.ErrFuture) + }) +} + +func TestGap(t *testing.T) { + // mockL1 starts at block 1 to produce a gap + expectedDerivedFrom := mockL1(1) + // mockL2 starts at block 2 to produce a gap + expectedDerived := mockL2(2) + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddDerived(toRef(expectedDerivedFrom, mockL1(0).Hash), toRef(expectedDerived, mockL2(0).Hash))) + }, + func(t *testing.T, db *DB, m *stubMetrics) { + _, _, err := db.NextDerived(mockL2(0).ID()) + require.ErrorIs(t, err, types.ErrSkipped) + + _, err = db.NextDerivedFrom(mockL1(0).ID()) + require.ErrorIs(t, err, types.ErrSkipped) + }) +} + +func TestThreeEntryDB(t *testing.T) { + l1Block0 := mockL1(0) + l1Block1 := mockL1(1) + l1Block2 := mockL1(2) + + l2Block0 := mockL2(0) + l2Block1 := mockL2(1) + l2Block2 := mockL2(2) + + runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddDerived(toRef(l1Block0, common.Hash{}), toRef(l2Block0, common.Hash{}))) + require.NoError(t, db.AddDerived(toRef(l1Block1, l1Block0.Hash), toRef(l2Block1, l2Block0.Hash))) + require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block2, l2Block1.Hash))) + }, func(t *testing.T, db *DB, m *stubMetrics) { + + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + require.Equal(t, l2Block2, derived) + + derivedFrom, derived, err = db.First() + require.NoError(t, err) + require.Equal(t, l1Block0, derivedFrom) + require.Equal(t, l2Block0, derived) + + derived, err = db.LastDerivedAt(l1Block2.ID()) + require.NoError(t, err) + require.Equal(t, l2Block2, derived) + + _, err = db.LastDerivedAt(eth.BlockID{Hash: common.Hash{0xaa}, Number: l1Block2.Number}) + require.ErrorIs(t, err, types.ErrConflict) + + derivedFrom, err = db.DerivedFrom(l2Block2.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + + _, err = db.DerivedFrom(eth.BlockID{Hash: common.Hash{0xbb}, Number: l2Block2.Number}) + require.ErrorIs(t, err, types.ErrConflict) + + derived, err = db.LastDerivedAt(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l2Block1, derived) + + derivedFrom, err = db.DerivedFrom(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + + derived, err = db.LastDerivedAt(l1Block0.ID()) + require.NoError(t, err) + require.Equal(t, l2Block0, derived) + + derivedFrom, err = db.DerivedFrom(l2Block0.ID()) + require.NoError(t, err) + require.Equal(t, l1Block0, derivedFrom) + + derived, err = db.PreviousDerived(l2Block0.ID()) + require.NoError(t, err) + require.Equal(t, types.BlockSeal{}, derived) + + derived, err = db.PreviousDerived(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l2Block0, derived) + + derived, err = db.PreviousDerived(l2Block2.ID()) + require.NoError(t, err) + require.Equal(t, l2Block1, derived) + + derivedFrom, derived, err = db.NextDerived(l2Block0.ID()) + require.NoError(t, err) + require.Equal(t, l2Block1, derived) + require.Equal(t, l1Block1, derivedFrom) + + derivedFrom, derived, err = db.NextDerived(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l2Block2, derived) + require.Equal(t, l1Block2, derivedFrom) + + _, _, err = db.NextDerived(l2Block2.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + derivedFrom, err = db.PreviousDerivedFrom(l1Block0.ID()) + require.NoError(t, err) + require.Equal(t, types.BlockSeal{}, derivedFrom) + + derivedFrom, err = db.PreviousDerivedFrom(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block0, derivedFrom) + + derivedFrom, err = db.PreviousDerivedFrom(l1Block2.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + + derivedFrom, err = db.NextDerivedFrom(l1Block0.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + + derivedFrom, err = db.NextDerivedFrom(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + + _, err = db.NextDerivedFrom(l1Block2.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + _, _, err = db.FirstAfter(l1Block2.ID(), l2Block2.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + derivedFrom, derived, err = db.FirstAfter(l1Block0.ID(), l2Block0.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + require.Equal(t, l2Block1, derived) + + derivedFrom, derived, err = db.FirstAfter(l1Block1.ID(), l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + require.Equal(t, l2Block2, derived) + }) +} + +// TestFastL2Batcher tests what happens if we have derived multiple L2 blocks from the same L1 block +func TestFastL2Batcher(t *testing.T) { + l1Block0 := mockL1(0) + l1Block1 := mockL1(1) + l1Block2 := mockL1(2) + + l2Block0 := mockL2(0) + l2Block1 := mockL2(1) + l2Block2 := mockL2(2) + l2Block3 := mockL2(3) + l2Block4 := mockL2(4) + l2Block5 := mockL2(5) + + runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) { + // L2 genesis derived from L1 genesis + require.NoError(t, db.AddDerived(toRef(l1Block0, common.Hash{}), toRef(l2Block0, common.Hash{}))) + // Many L2 blocks all derived from same L1 block + l1Ref1 := toRef(l1Block1, l1Block0.Hash) + require.NoError(t, db.AddDerived(l1Ref1, toRef(l2Block1, l2Block0.Hash))) + require.NoError(t, db.AddDerived(l1Ref1, toRef(l2Block2, l2Block1.Hash))) + require.NoError(t, db.AddDerived(l1Ref1, toRef(l2Block3, l2Block2.Hash))) + require.NoError(t, db.AddDerived(l1Ref1, toRef(l2Block4, l2Block3.Hash))) + // Latest L2 block derived from later L1 block + require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block5, l2Block4.Hash))) + }, func(t *testing.T, db *DB, m *stubMetrics) { + + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + require.Equal(t, l2Block5, derived) + + derived, err = db.LastDerivedAt(l1Block2.ID()) + require.NoError(t, err) + require.Equal(t, l2Block5, derived) + + // test what tip was derived from + derivedFrom, err = db.DerivedFrom(l2Block5.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + + // Multiple L2 blocks all derived from same older L1 block + for _, b := range []types.BlockSeal{l2Block1, l2Block2, l2Block3, l2Block4} { + derivedFrom, err = db.DerivedFrom(b.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + } + + // test that the latest L2 counts, not the intermediate + derived, err = db.LastDerivedAt(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l2Block4, derived) + + derived, err = db.PreviousDerived(l2Block5.ID()) + require.NoError(t, err) + require.Equal(t, l2Block4, derived) + derived, err = db.PreviousDerived(l2Block4.ID()) + require.NoError(t, err) + require.Equal(t, l2Block3, derived) + derived, err = db.PreviousDerived(l2Block3.ID()) + require.NoError(t, err) + require.Equal(t, l2Block2, derived) + derived, err = db.PreviousDerived(l2Block2.ID()) + require.NoError(t, err) + require.Equal(t, l2Block1, derived) + derived, err = db.PreviousDerived(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l2Block0, derived) + + derivedFrom, derived, err = db.NextDerived(l2Block0.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + require.Equal(t, l2Block1, derived) + derivedFrom, derived, err = db.NextDerived(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + require.Equal(t, l2Block2, derived) + derivedFrom, derived, err = db.NextDerived(l2Block2.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + require.Equal(t, l2Block3, derived) + derivedFrom, derived, err = db.NextDerived(l2Block3.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + require.Equal(t, l2Block4, derived) + derivedFrom, derived, err = db.NextDerived(l2Block4.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) // derived from later L1 block + require.Equal(t, l2Block5, derived) + _, _, err = db.NextDerived(l2Block5.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + derivedFrom, err = db.PreviousDerivedFrom(l1Block2.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + derivedFrom, err = db.PreviousDerivedFrom(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block0, derivedFrom) + + derivedFrom, err = db.NextDerivedFrom(l1Block0.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + derivedFrom, err = db.NextDerivedFrom(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + _, err = db.NextDerivedFrom(l1Block2.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + derivedFrom, derived, err = db.FirstAfter(l1Block1.ID(), l2Block2.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) // no increment in L1 yet, the next after is L2 block 3 + require.Equal(t, l2Block3, derived) + }) +} + +// TestSlowL2Batcher tests what happens if we have derived no new L2 blocks in a L1 block +func TestSlowL2Batcher(t *testing.T) { + l1Block0 := mockL1(0) + l1Block1 := mockL1(1) + l1Block2 := mockL1(2) + l1Block3 := mockL1(3) + l1Block4 := mockL1(4) + l1Block5 := mockL1(5) + + l2Block0 := mockL2(0) + l2Block1 := mockL2(1) + l2Block2 := mockL2(2) + + runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) { + // L2 genesis derived from L1 genesis + require.NoError(t, db.AddDerived(toRef(l1Block0, common.Hash{}), toRef(l2Block0, common.Hash{}))) + // Many L1 blocks all repeating the same L2 block + l2Ref1 := toRef(l2Block1, l2Block0.Hash) + require.NoError(t, db.AddDerived(toRef(l1Block1, l1Block0.Hash), l2Ref1)) + require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), l2Ref1)) + require.NoError(t, db.AddDerived(toRef(l1Block3, l1Block2.Hash), l2Ref1)) + require.NoError(t, db.AddDerived(toRef(l1Block4, l1Block3.Hash), l2Ref1)) + // New L1 block that finally produces a new L2 block + require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash))) + }, func(t *testing.T, db *DB, m *stubMetrics) { + + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block5, derivedFrom) + require.Equal(t, l2Block2, derived) + + // test what we last derived at the tip + derived, err = db.LastDerivedAt(l1Block5.ID()) + require.NoError(t, err) + require.Equal(t, l2Block2, derived) + + // Multiple L1 blocks all copying the last known derived L2 block + for _, b := range []types.BlockSeal{l1Block1, l1Block2, l1Block3, l1Block4} { + derived, err = db.LastDerivedAt(b.ID()) + require.NoError(t, err) + require.Equal(t, l2Block1, derived) + } + + // test that the first L1 counts, not the ones that repeat the L2 info + derivedFrom, err = db.DerivedFrom(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + + derived, err = db.PreviousDerived(l2Block2.ID()) + require.NoError(t, err) + require.Equal(t, l2Block1, derived) + derived, err = db.PreviousDerived(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l2Block0, derived) + + derivedFrom, derived, err = db.NextDerived(l2Block0.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + require.Equal(t, l2Block1, derived) + derivedFrom, derived, err = db.NextDerived(l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block5, derivedFrom) + require.Equal(t, l2Block2, derived) + _, _, err = db.NextDerived(l2Block2.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + derivedFrom, err = db.PreviousDerivedFrom(l1Block5.ID()) + require.NoError(t, err) + require.Equal(t, l1Block4, derivedFrom) + derivedFrom, err = db.PreviousDerivedFrom(l1Block4.ID()) + require.NoError(t, err) + require.Equal(t, l1Block3, derivedFrom) + derivedFrom, err = db.PreviousDerivedFrom(l1Block3.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + derivedFrom, err = db.PreviousDerivedFrom(l1Block2.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + derivedFrom, err = db.PreviousDerivedFrom(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block0, derivedFrom) + + derivedFrom, err = db.NextDerivedFrom(l1Block0.ID()) + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + derivedFrom, err = db.NextDerivedFrom(l1Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block2, derivedFrom) + derivedFrom, err = db.NextDerivedFrom(l1Block2.ID()) + require.NoError(t, err) + require.Equal(t, l1Block3, derivedFrom) + derivedFrom, err = db.NextDerivedFrom(l1Block4.ID()) + require.NoError(t, err) + require.Equal(t, l1Block5, derivedFrom) + _, err = db.NextDerivedFrom(l1Block5.ID()) + require.ErrorIs(t, err, types.ErrFuture) + + derivedFrom, derived, err = db.FirstAfter(l1Block2.ID(), l2Block1.ID()) + require.NoError(t, err) + require.Equal(t, l1Block3, derivedFrom) + require.Equal(t, l2Block1, derived) // no increment in L2 yet, the next after is L1 block 3 + }) +} + +func TestManyEntryDB(t *testing.T) { + t.Run("at genesis", func(t *testing.T) { + testManyEntryDB(t, 0, 0) + }) + t.Run("start at arbitrary L1 block", func(t *testing.T) { + testManyEntryDB(t, 30000, 0) + }) + t.Run("start at arbitrary L2 block", func(t *testing.T) { + testManyEntryDB(t, 0, 30000) + }) + t.Run("start at arbitrary L1 and L2 block", func(t *testing.T) { + testManyEntryDB(t, 2000000, 1000000) + }) +} + +func testManyEntryDB(t *testing.T, offsetL1 uint64, offsetL2 uint64) { + // L2 -> first L1 occurrence + firstDerivedFrom := make(map[eth.BlockID]types.BlockSeal) + // L1 -> last L2 occurrence + lastDerived := make(map[eth.BlockID]types.BlockSeal) + + runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) { + // Insert genesis + require.NoError(t, db.AddDerived(toRef(mockL1(offsetL1), common.Hash{}), toRef(mockL2(offsetL2), common.Hash{}))) + firstDerivedFrom[mockL2(offsetL2).ID()] = mockL1(offsetL1) + lastDerived[mockL1(offsetL1).ID()] = mockL2(offsetL2) + + rng := rand.New(rand.NewSource(1234)) + // Insert 1000 randomly generated entries, derived at random bumps in L1 + for i := uint64(0); i < 1000; i++ { + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + + switch rng.Intn(3) { + case 0: // bump L1 + derivedFrom = mockL1(derivedFrom.Number + 1) + case 1: // bump L2 + derived = mockL2(derived.Number + 1) + case 2: // bump both + derivedFrom = mockL1(derivedFrom.Number + 1) + derived = mockL2(derived.Number + 1) + } + derivedFromRef := toRef(derivedFrom, mockL1(derivedFrom.Number-1).Hash) + derivedRef := toRef(derived, mockL2(derived.Number-1).Hash) + lastDerived[derivedFromRef.ID()] = derived + if _, ok := firstDerivedFrom[derivedRef.ID()]; !ok { + firstDerivedFrom[derivedRef.ID()] = derivedFrom + } + require.NoError(t, db.AddDerived(derivedFromRef, derivedRef)) + } + }, func(t *testing.T, db *DB, m *stubMetrics) { + // Now assert we can find what they are all derived from, and match the expectations. + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.NotZero(t, derivedFrom.Number-offsetL1) + require.NotZero(t, derived.Number-offsetL2) + + for i := offsetL1; i <= derivedFrom.Number; i++ { + l1ID := mockL1(i).ID() + derived, err := db.LastDerivedAt(l1ID) + require.NoError(t, err) + require.Contains(t, lastDerived, l1ID) + require.Equal(t, lastDerived[l1ID], derived) + } + + for i := offsetL2; i <= derived.Number; i++ { + l2ID := mockL2(i).ID() + derivedFrom, err := db.DerivedFrom(l2ID) + require.NoError(t, err) + require.Contains(t, firstDerivedFrom, l2ID) + require.Equal(t, firstDerivedFrom[l2ID], derivedFrom) + } + + // if not started at genesis, try to read older data, assert it's unavailable. + if offsetL1 > 0 { + _, err := db.LastDerivedAt(mockL1(0).ID()) + require.ErrorIs(t, err, types.ErrSkipped) + + _, err = db.LastDerivedAt(mockL1(offsetL1 - 1).ID()) + require.ErrorIs(t, err, types.ErrSkipped) + } + if offsetL2 > 0 { + _, err := db.DerivedFrom(mockL2(0).ID()) + require.ErrorIs(t, err, types.ErrSkipped) + + _, err = db.DerivedFrom(mockL2(offsetL2 - 1).ID()) + require.ErrorIs(t, err, types.ErrSkipped) + } + }) +} + +// TestRewind tests what happens if we rewind +func TestRewind(t *testing.T) { + l1Block0 := mockL1(0) + l1Block1 := mockL1(1) + l1Block2 := mockL1(2) + l1Block3 := mockL1(3) + l1Block4 := mockL1(4) + l1Block5 := mockL1(5) + + l2Block0 := mockL2(0) + l2Block1 := mockL2(1) + l2Block2 := mockL2(2) + + runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) { + // L2 genesis derived from L1 genesis + require.NoError(t, db.AddDerived(toRef(l1Block0, common.Hash{}), toRef(l2Block0, common.Hash{}))) + // Many L1 blocks all repeating the same L2 block + l2Ref1 := toRef(l2Block1, l2Block0.Hash) + require.NoError(t, db.AddDerived(toRef(l1Block1, l1Block0.Hash), l2Ref1)) + require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), l2Ref1)) + require.NoError(t, db.AddDerived(toRef(l1Block3, l1Block2.Hash), l2Ref1)) + require.NoError(t, db.AddDerived(toRef(l1Block4, l1Block3.Hash), l2Ref1)) + // New L1 block that finally produces a new L2 block + require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash))) + }, func(t *testing.T, db *DB, m *stubMetrics) { + + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block5, derivedFrom) + require.Equal(t, l2Block2, derived) + + // Rewind to the future + require.ErrorIs(t, db.Rewind(6), types.ErrFuture) + + // Rewind to the exact block we're at + require.NoError(t, db.Rewind(l1Block5.Number)) + derivedFrom, derived, err = db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block5, derivedFrom) + require.Equal(t, l2Block2, derived) + + // Now rewind to L1 block 3 (inclusive). + require.NoError(t, db.Rewind(l1Block3.Number)) + + // See if we find consistent data + derivedFrom, derived, err = db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block3, derivedFrom) + require.Equal(t, l2Block1, derived) + + // Rewind further to L1 block 1 (inclusive). + require.NoError(t, db.Rewind(l1Block1.Number)) + derivedFrom, derived, err = db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block1, derivedFrom) + require.Equal(t, l2Block1, derived) + + // Rewind further to L1 block 0 (inclusive). + require.NoError(t, db.Rewind(l1Block0.Number)) + derivedFrom, derived, err = db.Latest() + require.NoError(t, err) + require.Equal(t, l1Block0, derivedFrom) + require.Equal(t, l2Block0, derived) + }) +} diff --git a/op-supervisor/supervisor/backend/db/fromda/entry.go b/op-supervisor/supervisor/backend/db/fromda/entry.go new file mode 100644 index 0000000000000..4e4f9876d3e8e --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/entry.go @@ -0,0 +1,95 @@ +package fromda + +import ( + "encoding/binary" + "fmt" + "io" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +const EntrySize = 100 + +type Entry [EntrySize]byte + +func (e Entry) Type() EntryType { + return EntryType(e[0]) +} + +type EntryType uint8 + +const ( + DerivedFromV0 EntryType = 0 +) + +func (s EntryType) String() string { + switch s { + case DerivedFromV0: + return "v0" + default: + return fmt.Sprintf("unknown(%d)", uint8(s)) + } +} + +type EntryBinary struct{} + +func (EntryBinary) Append(dest []byte, e *Entry) []byte { + return append(dest, e[:]...) +} + +func (EntryBinary) ReadAt(dest *Entry, r io.ReaderAt, at int64) (n int, err error) { + return r.ReadAt(dest[:], at) +} + +func (EntryBinary) EntrySize() int { + return EntrySize +} + +type LinkEntry struct { + derivedFrom types.BlockSeal + derived types.BlockSeal +} + +func (d LinkEntry) String() string { + return fmt.Sprintf("LinkEntry(derivedFrom: %s, derived: %s)", d.derivedFrom, d.derived) +} + +func (d *LinkEntry) decode(e Entry) error { + if e.Type() != DerivedFromV0 { + return fmt.Errorf("%w: unexpected entry type: %s", types.ErrDataCorruption, e.Type()) + } + if [3]byte(e[1:4]) != ([3]byte{}) { + return fmt.Errorf("%w: expected empty data, to pad entry size to round number: %x", types.ErrDataCorruption, e[1:4]) + } + offset := 4 + d.derivedFrom.Number = binary.BigEndian.Uint64(e[offset : offset+8]) + offset += 8 + d.derivedFrom.Timestamp = binary.BigEndian.Uint64(e[offset : offset+8]) + offset += 8 + d.derived.Number = binary.BigEndian.Uint64(e[offset : offset+8]) + offset += 8 + d.derived.Timestamp = binary.BigEndian.Uint64(e[offset : offset+8]) + offset += 8 + copy(d.derivedFrom.Hash[:], e[offset:offset+32]) + offset += 32 + copy(d.derived.Hash[:], e[offset:offset+32]) + return nil +} + +func (d *LinkEntry) encode() Entry { + var out Entry + out[0] = uint8(DerivedFromV0) + offset := 4 + binary.BigEndian.PutUint64(out[offset:offset+8], d.derivedFrom.Number) + offset += 8 + binary.BigEndian.PutUint64(out[offset:offset+8], d.derivedFrom.Timestamp) + offset += 8 + binary.BigEndian.PutUint64(out[offset:offset+8], d.derived.Number) + offset += 8 + binary.BigEndian.PutUint64(out[offset:offset+8], d.derived.Timestamp) + offset += 8 + copy(out[offset:offset+32], d.derivedFrom.Hash[:]) + offset += 32 + copy(out[offset:offset+32], d.derived.Hash[:]) + return out +} diff --git a/op-supervisor/supervisor/backend/db/fromda/entry_test.go b/op-supervisor/supervisor/backend/db/fromda/entry_test.go new file mode 100644 index 0000000000000..eedc07aaece8b --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/entry_test.go @@ -0,0 +1,43 @@ +package fromda + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func FuzzRoundtripLinkEntry(f *testing.F) { + f.Fuzz(func(t *testing.T, aHash []byte, aNum uint64, aTimestamp uint64, bHash []byte, bNum uint64, bTimestamp uint64) { + x := LinkEntry{ + derivedFrom: types.BlockSeal{ + Hash: common.BytesToHash(aHash), + Number: aNum, + Timestamp: aTimestamp, + }, + derived: types.BlockSeal{ + Hash: common.BytesToHash(bHash), + Number: bNum, + Timestamp: bTimestamp, + }, + } + entry := x.encode() + require.Equal(t, DerivedFromV0, entry.Type()) + var y LinkEntry + err := y.decode(entry) + require.NoError(t, err) + require.Equal(t, x, y) + }) +} + +func TestLinkEntry(t *testing.T) { + t.Run("invalid type", func(t *testing.T) { + var entry Entry + entry[0] = 123 + var x LinkEntry + require.ErrorContains(t, x.decode(entry), "unexpected") + }) +} diff --git a/op-supervisor/supervisor/backend/db/fromda/metrics.go b/op-supervisor/supervisor/backend/db/fromda/metrics.go new file mode 100644 index 0000000000000..f82bf0037ddb1 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/metrics.go @@ -0,0 +1,25 @@ +package fromda + +type Metrics interface { + RecordDBDerivedEntryCount(count int64) +} + +type ChainMetrics interface { + RecordDBEntryCount(kind string, count int64) +} + +type delegate struct { + inner ChainMetrics + kind string +} + +func (d *delegate) RecordDBDerivedEntryCount(count int64) { + d.inner.RecordDBEntryCount(d.kind, count) +} + +func AdaptMetrics(chainMetrics ChainMetrics, kind string) Metrics { + return &delegate{ + kind: kind, + inner: chainMetrics, + } +} diff --git a/op-supervisor/supervisor/backend/db/fromda/update.go b/op-supervisor/supervisor/backend/db/fromda/update.go new file mode 100644 index 0000000000000..957df9e2dfa7f --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/update.go @@ -0,0 +1,122 @@ +package fromda + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error { + db.rwLock.Lock() + defer db.rwLock.Unlock() + + // If we don't have any entries yet, allow any block to start things off + if db.store.Size() == 0 { + link := LinkEntry{ + derivedFrom: types.BlockSeal{ + Hash: derivedFrom.Hash, + Number: derivedFrom.Number, + Timestamp: derivedFrom.Time, + }, + derived: types.BlockSeal{ + Hash: derived.Hash, + Number: derived.Number, + Timestamp: derived.Time, + }, + } + e := link.encode() + if err := db.store.Append(e); err != nil { + return err + } + db.m.RecordDBDerivedEntryCount(db.store.Size()) + return nil + } + + lastDerivedFrom, lastDerived, err := db.latest() + if err != nil { + return err + } + + if lastDerived.ID() == derived.ID() && lastDerivedFrom.ID() == derivedFrom.ID() { + // it shouldn't be possible, but the ID component of a block ref doesn't include the timestamp + // so if the timestampt doesn't match, still return no error to the caller, but at least log a warning + if lastDerived.Timestamp != derived.Time { + db.log.Warn("Derived block already exists with different timestamp", "derived", derived, "lastDerived", lastDerived) + } + if lastDerivedFrom.Timestamp != derivedFrom.Time { + db.log.Warn("Derived-from block already exists with different timestamp", "derivedFrom", derivedFrom, "lastDerivedFrom", lastDerivedFrom) + } + // Repeat of same information. No entries to be written. + // But we can silently ignore and not return an error, as that brings the caller + // in a consistent state, after which it can insert the actual new derived-from information. + return nil + } + + // Check derived relation: the L2 chain has to be sequential without gaps. An L2 block may repeat if the L1 block is empty. + if lastDerived.Number == derived.Number { + // Same block height? Then it must be the same block. + // I.e. we encountered an empty L1 block, and the same L2 block continues to be the last block that was derived from it. + if lastDerived.Hash != derived.Hash { + return fmt.Errorf("derived block %s conflicts with known derived block %s at same height: %w", + derived, lastDerived, types.ErrConflict) + } + } else if lastDerived.Number+1 == derived.Number { + if lastDerived.Hash != derived.ParentHash { + return fmt.Errorf("derived block %s (parent %s) does not build on %s: %w", + derived, derived.ParentHash, lastDerived, types.ErrConflict) + } + } else if lastDerived.Number+1 < derived.Number { + return fmt.Errorf("cannot add block (%s derived from %s), last block (%s derived from %s) is too far behind: (%w)", + derived, derivedFrom, + lastDerived, lastDerivedFrom, + types.ErrOutOfOrder) + } else { + return fmt.Errorf("derived block %s is older than current derived block %s: %w", + derived, lastDerived, types.ErrOutOfOrder) + } + + // Check derived-from relation: multiple L2 blocks may be derived from the same L1 block. But everything in sequence. + if lastDerivedFrom.Number == derivedFrom.Number { + // Same block height? Then it must be the same block. + if lastDerivedFrom.Hash != derivedFrom.Hash { + return fmt.Errorf("cannot add block %s as derived from %s, expected to be derived from %s at this block height: %w", + derived, derivedFrom, lastDerivedFrom, types.ErrConflict) + } + } else if lastDerivedFrom.Number+1 == derivedFrom.Number { + // parent hash check + if lastDerivedFrom.Hash != derivedFrom.ParentHash { + return fmt.Errorf("cannot add block %s as derived from %s (parent %s) derived on top of %s: %w", + derived, derivedFrom, derivedFrom.ParentHash, lastDerivedFrom, types.ErrConflict) + } + } else if lastDerivedFrom.Number+1 < derivedFrom.Number { + // adding block that is derived from something too far into the future + return fmt.Errorf("cannot add block (%s derived from %s), last block (%s derived from %s) is too far behind: (%w)", + derived, derivedFrom, + lastDerived, lastDerivedFrom, + types.ErrOutOfOrder) + } else { + // adding block that is derived from something too old + return fmt.Errorf("cannot add block %s as derived from %s, deriving already at %s: %w", + derived, derivedFrom, lastDerivedFrom, types.ErrOutOfOrder) + } + + link := LinkEntry{ + derivedFrom: types.BlockSeal{ + Hash: derivedFrom.Hash, + Number: derivedFrom.Number, + Timestamp: derivedFrom.Time, + }, + derived: types.BlockSeal{ + Hash: derived.Hash, + Number: derived.Number, + Timestamp: derived.Time, + }, + } + e := link.encode() + if err := db.store.Append(e); err != nil { + return err + } + db.m.RecordDBDerivedEntryCount(db.store.Size()) + return nil +} diff --git a/op-supervisor/supervisor/backend/db/fromda/update_test.go b/op-supervisor/supervisor/backend/db/fromda/update_test.go new file mode 100644 index 0000000000000..10b6b3bdd0533 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/fromda/update_test.go @@ -0,0 +1,180 @@ +package fromda + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type testCase struct { + name string + setupFn setupFn + assertFn assertFn +} + +func TestBadUpdates(t *testing.T) { + aDerivedFrom := mockL1(1) + aDerived := mockL2(201) + bDerivedFrom := mockL1(2) + bDerived := mockL2(202) + cDerivedFrom := mockL1(3) + cDerived := mockL2(203) + dDerivedFrom := mockL1(4) + dDerived := mockL2(204) + eDerivedFrom := mockL1(5) + eDerived := mockL2(205) + fDerivedFrom := mockL1(6) + fDerived := mockL2(206) + + noChange := assertFn(func(t *testing.T, db *DB, m *stubMetrics) { + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.Equal(t, dDerivedFrom, derivedFrom) + require.Equal(t, dDerived, derived) + }) + + testCases := []testCase{ + { + name: "add on old derivedFrom", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(bDerivedFrom, aDerivedFrom.Hash), toRef(dDerived, cDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "repeat parent derivedFrom", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(cDerivedFrom, bDerivedFrom.Hash), toRef(dDerived, cDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "add on conflicting derivedFrom, same height. And new derived value", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(types.BlockSeal{ + Hash: common.Hash{0xba, 0xd}, + Number: dDerivedFrom.Number, + Timestamp: dDerivedFrom.Timestamp, + }, cDerivedFrom.Hash), toRef(eDerived, dDerived.Hash)), types.ErrConflict) + }, + assertFn: noChange, + }, + { + name: "CrossDerivedFrom with conflicting parent root, same L1 height, new L2: accepted, L1 parent-hash is used only on L1 increments.", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddDerived(toRef(dDerivedFrom, common.Hash{0x42}), toRef(eDerived, dDerived.Hash)), types.ErrConflict) + }, + assertFn: func(t *testing.T, db *DB, m *stubMetrics) { + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.Equal(t, dDerivedFrom, derivedFrom) + require.Equal(t, eDerived, derived) + }, + }, + { + name: "Conflicting derivedFrom parent root, new L1 height, same L2", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(eDerivedFrom, common.Hash{0x42}), toRef(dDerived, cDerived.Hash)), types.ErrConflict) + }, + assertFn: noChange, + }, + { + name: "add on too new derivedFrom (even if parent-hash looks correct)", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(fDerivedFrom, dDerivedFrom.Hash), toRef(eDerived, dDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "add on old derivedFrom (even if parent-hash looks correct)", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(cDerivedFrom, bDerivedFrom.Hash), toRef(cDerived, dDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "add on even older derivedFrom", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(bDerivedFrom, aDerivedFrom.Hash), toRef(dDerived, cDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "add on conflicting derived, same L2 height, new L1 block", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(eDerivedFrom, dDerivedFrom.Hash), toRef(types.BlockSeal{ + Hash: common.Hash{0x42}, + Number: dDerived.Number, + Timestamp: dDerived.Timestamp, + }, cDerived.Hash)), types.ErrConflict) + }, + assertFn: noChange, + }, + { + name: "add derived with conflicting parent hash, new L1 height, same L2 height: accepted, L2 parent-hash is only checked on L2 increments.", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.NoError(t, db.AddDerived(toRef(eDerivedFrom, dDerivedFrom.Hash), toRef(dDerived, common.Hash{0x42})), types.ErrConflict) + }, + assertFn: func(t *testing.T, db *DB, m *stubMetrics) { + derivedFrom, derived, err := db.Latest() + require.NoError(t, err) + require.Equal(t, eDerivedFrom, derivedFrom) + require.Equal(t, dDerived, derived) + }, + }, + { + name: "add derived with conflicting parent hash, same L1 height, new L2 height", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(dDerivedFrom, cDerivedFrom.Hash), toRef(eDerived, common.Hash{0x42})), types.ErrConflict) + }, + assertFn: noChange, + }, + { + name: "add on too new derived (even if parent-hash looks correct)", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(dDerivedFrom, cDerivedFrom.Hash), toRef(fDerived, dDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "add on old derived (even if parent-hash looks correct)", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(dDerivedFrom, cDerivedFrom.Hash), toRef(cDerived, bDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "add on even older derived", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + require.ErrorIs(t, db.AddDerived(toRef(dDerivedFrom, cDerivedFrom.Hash), toRef(bDerived, aDerived.Hash)), types.ErrOutOfOrder) + }, + assertFn: noChange, + }, + { + name: "repeat self, silent no-op", + setupFn: func(t *testing.T, db *DB, m *stubMetrics) { + pre := m.DBDerivedEntryCount + require.NoError(t, db.AddDerived(toRef(dDerivedFrom, cDerivedFrom.Hash), toRef(dDerived, cDerived.Hash)), types.ErrOutOfOrder) + require.Equal(t, pre, m.DBDerivedEntryCount) + }, + assertFn: noChange, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runDBTest(t, + func(t *testing.T, db *DB, m *stubMetrics) { + // Good first entry + require.NoError(t, db.AddDerived(toRef(dDerivedFrom, cDerivedFrom.Hash), toRef(dDerived, cDerived.Hash))) + // apply the test-case setup + tc.setupFn(t, db, m) + }, + tc.assertFn) + }) + } +} diff --git a/op-supervisor/supervisor/backend/db/heads/heads.go b/op-supervisor/supervisor/backend/db/heads/heads.go deleted file mode 100644 index 93d02a84fa644..0000000000000 --- a/op-supervisor/supervisor/backend/db/heads/heads.go +++ /dev/null @@ -1,161 +0,0 @@ -package heads - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "sync" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/op-service/ioutil" - "github.com/ethereum-optimism/optimism/op-service/jsonutil" - - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -// HeadTracker records the current chain head pointers for a single chain. -type HeadTracker struct { - rwLock sync.RWMutex - - path string - - current *Heads - - logger log.Logger -} - -func (t *HeadTracker) CrossUnsafe(id types.ChainID) HeadPointer { - return t.current.Get(id).CrossUnsafe -} - -func (t *HeadTracker) CrossSafe(id types.ChainID) HeadPointer { - return t.current.Get(id).CrossSafe -} - -func (t *HeadTracker) CrossFinalized(id types.ChainID) HeadPointer { - return t.current.Get(id).CrossFinalized -} - -func (t *HeadTracker) LocalUnsafe(id types.ChainID) HeadPointer { - return t.current.Get(id).Unsafe -} - -func (t *HeadTracker) LocalSafe(id types.ChainID) HeadPointer { - return t.current.Get(id).LocalSafe -} - -func (t *HeadTracker) LocalFinalized(id types.ChainID) HeadPointer { - return t.current.Get(id).LocalFinalized -} - -func (t *HeadTracker) UpdateCrossUnsafe(id types.ChainID, pointer HeadPointer) error { - return t.Apply(OperationFn(func(heads *Heads) error { - t.logger.Info("Cross-unsafe update", "pointer", pointer) - h := heads.Get(id) - h.CrossUnsafe = pointer - heads.Put(id, h) - return nil - })) -} - -func (t *HeadTracker) UpdateCrossSafe(id types.ChainID, pointer HeadPointer) error { - return t.Apply(OperationFn(func(heads *Heads) error { - t.logger.Info("Cross-safe update", "pointer", pointer) - h := heads.Get(id) - h.CrossSafe = pointer - heads.Put(id, h) - return nil - })) -} - -func (t *HeadTracker) UpdateCrossFinalized(id types.ChainID, pointer HeadPointer) error { - return t.Apply(OperationFn(func(heads *Heads) error { - t.logger.Info("Cross-finalized update", "pointer", pointer) - h := heads.Get(id) - h.CrossFinalized = pointer - heads.Put(id, h) - return nil - })) -} - -func (t *HeadTracker) UpdateLocalUnsafe(id types.ChainID, pointer HeadPointer) error { - return t.Apply(OperationFn(func(heads *Heads) error { - t.logger.Info("Local-unsafe update", "pointer", pointer) - h := heads.Get(id) - h.Unsafe = pointer - heads.Put(id, h) - return nil - })) -} - -func (t *HeadTracker) UpdateLocalSafe(id types.ChainID, pointer HeadPointer) error { - return t.Apply(OperationFn(func(heads *Heads) error { - t.logger.Info("Local-safe update", "pointer", pointer) - h := heads.Get(id) - h.LocalSafe = pointer - heads.Put(id, h) - return nil - })) -} - -func (t *HeadTracker) UpdateLocalFinalized(id types.ChainID, pointer HeadPointer) error { - return t.Apply(OperationFn(func(heads *Heads) error { - t.logger.Info("Local-finalized update", "pointer", pointer) - h := heads.Get(id) - h.LocalFinalized = pointer - heads.Put(id, h) - return nil - })) -} - -func NewHeadTracker(logger log.Logger, path string) (*HeadTracker, error) { - current := NewHeads() - if data, err := os.ReadFile(path); errors.Is(err, os.ErrNotExist) { - // No existing file, just use empty heads - } else if err != nil { - return nil, fmt.Errorf("failed to read existing heads from %v: %w", path, err) - } else { - if err := json.Unmarshal(data, current); err != nil { - return nil, fmt.Errorf("invalid existing heads file %v: %w", path, err) - } - } - return &HeadTracker{ - path: path, - current: current, - logger: logger, - }, nil -} - -func (t *HeadTracker) Apply(op Operation) error { - t.rwLock.Lock() - defer t.rwLock.Unlock() - // Store a copy of the heads prior to changing so we can roll back if needed. - modified := t.current.Copy() - if err := op.Apply(modified); err != nil { - return fmt.Errorf("operation failed: %w", err) - } - if err := t.write(modified); err != nil { - return fmt.Errorf("failed to store updated heads: %w", err) - } - t.current = modified - return nil -} - -func (t *HeadTracker) Current() *Heads { - t.rwLock.RLock() - defer t.rwLock.RUnlock() - return t.current.Copy() -} - -func (t *HeadTracker) write(heads *Heads) error { - if err := jsonutil.WriteJSON(heads, ioutil.ToAtomicFile(t.path, 0o644)); err != nil { - return fmt.Errorf("failed to write new heads: %w", err) - } - return nil -} - -func (t *HeadTracker) Close() error { - return nil -} diff --git a/op-supervisor/supervisor/backend/db/heads/heads_test.go b/op-supervisor/supervisor/backend/db/heads/heads_test.go deleted file mode 100644 index 9b8fb4bd45726..0000000000000 --- a/op-supervisor/supervisor/backend/db/heads/heads_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package heads - -/* -import ( - "errors" - "os" - "path/filepath" - "testing" - - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/stretchr/testify/require" -) - -func TestHeads_SaveAndReload(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "heads.json") - chainA := types.ChainIDFromUInt64(3) - chainAHeads := ChainHeads{ - Unsafe: 1, - CrossUnsafe: 2, - LocalSafe: 3, - CrossSafe: 4, - LocalFinalized: 5, - CrossFinalized: 6, - } - chainB := types.ChainIDFromUInt64(5) - chainBHeads := ChainHeads{ - Unsafe: 11, - CrossUnsafe: 12, - LocalSafe: 13, - CrossSafe: 14, - LocalFinalized: 15, - CrossFinalized: 16, - } - - orig, err := NewHeadTracker(path) - require.NoError(t, err) - err = orig.Apply(OperationFn(func(heads *Heads) error { - heads.Put(chainA, chainAHeads) - heads.Put(chainB, chainBHeads) - return nil - })) - require.NoError(t, err) - require.Equal(t, orig.Current().Get(chainA), chainAHeads) - require.Equal(t, orig.Current().Get(chainB), chainBHeads) - - loaded, err := NewHeadTracker(path) - require.NoError(t, err) - require.EqualValues(t, loaded.Current(), orig.Current()) -} - -func TestHeads_NoChangesMadeIfOperationFails(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "heads.json") - chainA := types.ChainIDFromUInt64(3) - chainAHeads := ChainHeads{ - Unsafe: 1, - CrossUnsafe: 2, - LocalSafe: 3, - CrossSafe: 4, - LocalFinalized: 5, - CrossFinalized: 6, - } - - orig, err := NewHeadTracker(path) - require.NoError(t, err) - boom := errors.New("boom") - err = orig.Apply(OperationFn(func(heads *Heads) error { - heads.Put(chainA, chainAHeads) - return boom - })) - require.ErrorIs(t, err, boom) - require.Equal(t, ChainHeads{}, orig.Current().Get(chainA)) - - // Should be able to load from disk too - loaded, err := NewHeadTracker(path) - require.NoError(t, err) - require.EqualValues(t, loaded.Current(), orig.Current()) -} - -func TestHeads_NoChangesMadeIfWriteFails(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "invalid/heads.json") - chainA := types.ChainIDFromUInt64(3) - chainAHeads := ChainHeads{ - Unsafe: 1, - CrossUnsafe: 2, - LocalSafe: 3, - CrossSafe: 4, - LocalFinalized: 5, - CrossFinalized: 6, - } - - orig, err := NewHeadTracker(path) - require.NoError(t, err) - err = orig.Apply(OperationFn(func(heads *Heads) error { - heads.Put(chainA, chainAHeads) - return nil - })) - require.ErrorIs(t, err, os.ErrNotExist) - require.Equal(t, ChainHeads{}, orig.Current().Get(chainA)) -} -*/ diff --git a/op-supervisor/supervisor/backend/db/heads/types.go b/op-supervisor/supervisor/backend/db/heads/types.go deleted file mode 100644 index 7db0bff2d1062..0000000000000 --- a/op-supervisor/supervisor/backend/db/heads/types.go +++ /dev/null @@ -1,127 +0,0 @@ -package heads - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -type HeadPointer struct { - // LastSealedBlockHash is the last fully-processed block - LastSealedBlockHash common.Hash - LastSealedBlockNum uint64 - LastSealedTimestamp uint64 - - // Number of logs that have been verified since the LastSealedBlock. - // These logs are contained in the block that builds on top of the LastSealedBlock. - LogsSince uint32 -} - -// WithinRange checks if the given log, in the given block, -// is within range (i.e. before or equal to the head-pointer). -// This does not guarantee that the log exists. -func (ptr *HeadPointer) WithinRange(blockNum uint64, logIdx uint32) bool { - if ptr.LastSealedBlockHash == (common.Hash{}) { - return false // no block yet - } - return blockNum <= ptr.LastSealedBlockNum || - (blockNum+1 == ptr.LastSealedBlockNum && logIdx < ptr.LogsSince) -} - -func (ptr *HeadPointer) IsSealed(blockNum uint64) bool { - if ptr.LastSealedBlockHash == (common.Hash{}) { - return false // no block yet - } - return blockNum <= ptr.LastSealedBlockNum -} - -// ChainHeads provides the serialization format for the current chain heads. -type ChainHeads struct { - Unsafe HeadPointer `json:"localUnsafe"` - CrossUnsafe HeadPointer `json:"crossUnsafe"` - LocalSafe HeadPointer `json:"localSafe"` - CrossSafe HeadPointer `json:"crossSafe"` - LocalFinalized HeadPointer `json:"localFinalized"` - CrossFinalized HeadPointer `json:"crossFinalized"` -} - -type Heads struct { - Chains map[types.ChainID]ChainHeads -} - -func NewHeads() *Heads { - return &Heads{Chains: make(map[types.ChainID]ChainHeads)} -} - -func (h *Heads) Get(id types.ChainID) ChainHeads { - chain, ok := h.Chains[id] - if !ok { - return ChainHeads{} - } - // init to genesis - if chain.LocalFinalized == (HeadPointer{}) && chain.Unsafe.LastSealedBlockNum == 0 { - chain.LocalFinalized = chain.Unsafe - } - // Make sure the data is consistent - if chain.LocalSafe == (HeadPointer{}) { - chain.LocalSafe = chain.LocalFinalized - } - if chain.Unsafe == (HeadPointer{}) { - chain.Unsafe = chain.LocalSafe - } - if chain.CrossFinalized == (HeadPointer{}) && chain.LocalFinalized.LastSealedBlockNum == 0 { - chain.CrossFinalized = chain.LocalFinalized - } - if chain.CrossSafe == (HeadPointer{}) { - chain.CrossSafe = chain.CrossFinalized - } - if chain.CrossUnsafe == (HeadPointer{}) { - chain.CrossUnsafe = chain.CrossSafe - } - return chain -} - -func (h *Heads) Put(id types.ChainID, head ChainHeads) { - h.Chains[id] = head -} - -func (h *Heads) Copy() *Heads { - c := &Heads{Chains: make(map[types.ChainID]ChainHeads)} - for id, heads := range h.Chains { - c.Chains[id] = heads - } - return c -} - -func (h *Heads) MarshalJSON() ([]byte, error) { - data := make(map[hexutil.U256]ChainHeads) - for id, heads := range h.Chains { - data[hexutil.U256(id)] = heads - } - return json.Marshal(data) -} - -func (h *Heads) UnmarshalJSON(data []byte) error { - hexData := make(map[hexutil.U256]ChainHeads) - if err := json.Unmarshal(data, &hexData); err != nil { - return err - } - h.Chains = make(map[types.ChainID]ChainHeads) - for id, heads := range hexData { - h.Put(types.ChainID(id), heads) - } - return nil -} - -type Operation interface { - Apply(head *Heads) error -} - -type OperationFn func(heads *Heads) error - -func (f OperationFn) Apply(heads *Heads) error { - return f(heads) -} diff --git a/op-supervisor/supervisor/backend/db/heads/types_test.go b/op-supervisor/supervisor/backend/db/heads/types_test.go deleted file mode 100644 index 20bb057954166..0000000000000 --- a/op-supervisor/supervisor/backend/db/heads/types_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package heads - -import ( - "encoding/json" - "fmt" - "math/rand" // nosemgrep - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/common" - - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -func TestHeads(t *testing.T) { - rng := rand.New(rand.NewSource(1234)) - randHeadPtr := func() HeadPointer { - var h common.Hash - rng.Read(h[:]) - return HeadPointer{ - LastSealedBlockHash: h, - LastSealedBlockNum: rng.Uint64(), - LogsSince: rng.Uint32(), - } - } - t.Run("RoundTripViaJson", func(t *testing.T) { - heads := NewHeads() - heads.Put(types.ChainIDFromUInt64(3), ChainHeads{ - Unsafe: randHeadPtr(), - CrossUnsafe: randHeadPtr(), - LocalSafe: randHeadPtr(), - CrossSafe: randHeadPtr(), - LocalFinalized: randHeadPtr(), - CrossFinalized: randHeadPtr(), - }) - heads.Put(types.ChainIDFromUInt64(9), ChainHeads{ - Unsafe: randHeadPtr(), - CrossUnsafe: randHeadPtr(), - LocalSafe: randHeadPtr(), - CrossSafe: randHeadPtr(), - LocalFinalized: randHeadPtr(), - CrossFinalized: randHeadPtr(), - }) - heads.Put(types.ChainIDFromUInt64(4892497242424), ChainHeads{ - Unsafe: randHeadPtr(), - CrossUnsafe: randHeadPtr(), - LocalSafe: randHeadPtr(), - CrossSafe: randHeadPtr(), - LocalFinalized: randHeadPtr(), - CrossFinalized: randHeadPtr(), - }) - - j, err := json.Marshal(heads) - require.NoError(t, err) - - fmt.Println(string(j)) - var result Heads - err = json.Unmarshal(j, &result) - require.NoError(t, err) - require.Equal(t, heads.Chains, result.Chains) - }) - - t.Run("Copy", func(t *testing.T) { - chainA := types.ChainIDFromUInt64(3) - chainB := types.ChainIDFromUInt64(4) - chainAOrigHeads := ChainHeads{ - Unsafe: randHeadPtr(), - } - chainAModifiedHeads1 := ChainHeads{ - Unsafe: randHeadPtr(), - } - chainAModifiedHeads2 := ChainHeads{ - Unsafe: randHeadPtr(), - } - chainBModifiedHeads := ChainHeads{ - Unsafe: randHeadPtr(), - } - - heads := NewHeads() - heads.Put(chainA, chainAOrigHeads) - - otherHeads := heads.Copy() - otherHeads.Put(chainA, chainAModifiedHeads1) - otherHeads.Put(chainB, chainBModifiedHeads) - - require.Equal(t, heads.Get(chainA), chainAOrigHeads) - require.Equal(t, heads.Get(chainB), ChainHeads{}) - - heads.Put(chainA, chainAModifiedHeads2) - require.Equal(t, heads.Get(chainA), chainAModifiedHeads2) - - require.Equal(t, otherHeads.Get(chainA), chainAModifiedHeads1) - require.Equal(t, otherHeads.Get(chainB), chainBModifiedHeads) - }) -} diff --git a/op-supervisor/supervisor/backend/db/logs/db.go b/op-supervisor/supervisor/backend/db/logs/db.go index 10863c052645e..64e61c6bebf76 100644 --- a/op-supervisor/supervisor/backend/db/logs/db.go +++ b/op-supervisor/supervisor/backend/db/logs/db.go @@ -20,35 +20,11 @@ const ( eventFlagHasExecutingMessage = byte(1) ) -var ( - // ErrLogOutOfOrder happens when you try to add a log to the DB, - // but it does not actually fit onto the latest data (by being too old or new). - ErrLogOutOfOrder = errors.New("log out of order") - // ErrDataCorruption happens when the underlying DB has some I/O issue - ErrDataCorruption = errors.New("data corruption") - // ErrSkipped happens when we try to retrieve data that is not available (pruned) - // It may also happen if we erroneously skip data, that was not considered a conflict, if the DB is corrupted. - ErrSkipped = errors.New("skipped data") - // ErrFuture happens when data is just not yet available - ErrFuture = errors.New("future data") - // ErrConflict happens when we know for sure that there is different canonical data - ErrConflict = errors.New("conflicting data") -) - type Metrics interface { - RecordDBEntryCount(count int64) + RecordDBEntryCount(kind string, count int64) RecordDBSearchEntriesRead(count int64) } -type EntryStore interface { - Size() int64 - LastEntryIdx() entrydb.EntryIdx - Read(idx entrydb.EntryIdx) (entrydb.Entry, error) - Append(entries ...entrydb.Entry) error - Truncate(idx entrydb.EntryIdx) error - Close() error -} - // DB implements an append only database for log data and cross-chain dependencies. // // To keep the append-only format, reduce data size, and support reorg detection and registering of executing-messages: @@ -59,21 +35,21 @@ type EntryStore interface { type DB struct { log log.Logger m Metrics - store EntryStore + store entrydb.EntryStore[EntryType, Entry] rwLock sync.RWMutex lastEntryContext logContext } func NewFromFile(logger log.Logger, m Metrics, path string, trimToLastSealed bool) (*DB, error) { - store, err := entrydb.NewEntryDB(logger, path) + store, err := entrydb.NewEntryDB[EntryType, Entry, EntryBinary](logger, path) if err != nil { return nil, fmt.Errorf("failed to open DB: %w", err) } return NewFromEntryStore(logger, m, store, trimToLastSealed) } -func NewFromEntryStore(logger log.Logger, m Metrics, store EntryStore, trimToLastSealed bool) (*DB, error) { +func NewFromEntryStore(logger log.Logger, m Metrics, store entrydb.EntryStore[EntryType, Entry], trimToLastSealed bool) (*DB, error) { db := &DB{ log: logger, m: m, @@ -117,7 +93,7 @@ func (db *DB) init(trimToLastSealed bool) error { // and then apply any remaining changes on top, to hydrate the state. lastCheckpoint := (db.lastEntryIdx() / searchCheckpointFrequency) * searchCheckpointFrequency i := db.newIterator(lastCheckpoint) - i.current.need.Add(entrydb.FlagCanonicalHash) + i.current.need.Add(FlagCanonicalHash) if err := i.End(); err != nil { return fmt.Errorf("failed to init from remaining trailing data: %w", err) } @@ -132,7 +108,7 @@ func (db *DB) trimToLastSealed() error { if err != nil { return fmt.Errorf("failed to read %v to check for trailing entries: %w", i, err) } - if entry.Type() == entrydb.TypeCanonicalHash { + if entry.Type() == TypeCanonicalHash { // only an executing hash, indicating a sealed block, is a valid point for restart break } @@ -146,7 +122,7 @@ func (db *DB) trimToLastSealed() error { } func (db *DB) updateEntryCountMetric() { - db.m.RecordDBEntryCount(db.store.Size()) + db.m.RecordDBEntryCount("log", db.store.Size()) } func (db *DB) IteratorStartingAt(sealedNum uint64, logsSince uint32) (Iterator, error) { @@ -159,23 +135,114 @@ func (db *DB) IteratorStartingAt(sealedNum uint64, logsSince uint32) (Iterator, // returning the next index after it where things continue from. // returns ErrFuture if the block is too new to be able to tell // returns ErrDifferent if the known block does not match -func (db *DB) FindSealedBlock(block eth.BlockID) (nextEntry entrydb.EntryIdx, err error) { +func (db *DB) FindSealedBlock(number uint64) (seal types.BlockSeal, err error) { db.rwLock.RLock() defer db.rwLock.RUnlock() - iter, err := db.newIteratorAt(block.Number, 0) - if errors.Is(err, ErrFuture) { - return 0, fmt.Errorf("block %d is not known yet: %w", block.Number, ErrFuture) + iter, err := db.newIteratorAt(number, 0) + if errors.Is(err, types.ErrFuture) { + return types.BlockSeal{}, fmt.Errorf("block %d is not known yet: %w", number, types.ErrFuture) } else if err != nil { - return 0, fmt.Errorf("failed to find sealed block %d: %w", block.Number, err) + return types.BlockSeal{}, fmt.Errorf("failed to find sealed block %d: %w", number, err) } - h, _, ok := iter.SealedBlock() + h, n, ok := iter.SealedBlock() if !ok { panic("expected block") } - if block.Hash != h { - return 0, fmt.Errorf("queried %s but got %s at number %d: %w", block.Hash, h, block.Number, ErrConflict) + if n != number { + panic(fmt.Errorf("found block seal %s %d does not match expected block number %d", h, n, number)) + } + timestamp, ok := iter.SealedTimestamp() + if !ok { + panic("expected timestamp") } - return iter.NextIndex(), nil + return types.BlockSeal{ + Hash: h, + Number: n, + Timestamp: timestamp, + }, nil +} + +// StartingBlock returns the first block seal in the DB, if any. +func (db *DB) StartingBlock() (seal types.BlockSeal, err error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + iter := db.newIterator(0) + if err := iter.NextBlock(); err != nil { + return types.BlockSeal{}, err + } + h, n, _ := iter.SealedBlock() + t, _ := iter.SealedTimestamp() + return types.BlockSeal{ + Hash: h, + Number: n, + Timestamp: t, + }, err +} + +// OpenBlock returns the Executing Messages for the block at the given number. +// it returns identification of the block, the parent block, and the executing messages. +func (db *DB) OpenBlock(blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, retErr error) { + db.rwLock.RLock() + defer db.rwLock.RUnlock() + + if blockNum == 0 { + seal, err := db.StartingBlock() + if err != nil { + retErr = err + return + } + ref = eth.BlockRef{ + Hash: seal.Hash, + Number: seal.Number, + ParentHash: common.Hash{}, + Time: seal.Timestamp, + } + logCount = 0 + execMsgs = nil + return + } + + // start at the first log (if any) after the block-seal of the parent block + blockIter, err := db.newIteratorAt(blockNum-1, 0) + if err != nil { + retErr = err + return + } + // register the parent block + parentHash, _, ok := blockIter.SealedBlock() + if ok { + ref.ParentHash = parentHash + } + // walk to the end of the block, and remember what we see in the block. + logCount = 0 + execMsgs = make(map[uint32]*types.ExecutingMessage, 0) + retErr = blockIter.TraverseConditional(func(state IteratorState) error { + _, logIndex, ok := state.InitMessage() + if ok { + logCount = logIndex + 1 + } + if m := state.ExecMessage(); m != nil { + execMsgs[logIndex] = m + } + h, n, ok := state.SealedBlock() + if !ok { + return nil + } + if n == blockNum { + ref.Number = n + ref.Hash = h + ref.Time, _ = state.SealedTimestamp() + return types.ErrStop + } + if n > blockNum { + return fmt.Errorf("expected to run into block %d, but did not find it, found %d: %w", blockNum, n, types.ErrDataCorruption) + } + return nil + }) + if errors.Is(retErr, types.ErrStop) { + retErr = nil + } + return } // LatestSealedBlockNum returns the block number of the block that was last sealed, @@ -206,31 +273,64 @@ func (db *DB) Get(blockNum uint64, logIdx uint32) (common.Hash, error) { // If the log is determined to conflict with the canonical chain, then ErrConflict is returned. // logIdx is the index of the log in the array of all logs in the block. // This can be used to check the validity of cross-chain interop events. -func (db *DB) Contains(blockNum uint64, logIdx uint32, logHash common.Hash) (entrydb.EntryIdx, error) { +// The block-seal of the blockNum block, that the log was included in, is returned. +// This seal may be fully zeroed, without error, if the block isn't fully known yet. +func (db *DB) Contains(blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { db.rwLock.RLock() defer db.rwLock.RUnlock() db.log.Trace("Checking for log", "blockNum", blockNum, "logIdx", logIdx, "hash", logHash) + // Hot-path: check if we have the block + if db.lastEntryContext.hasCompleteBlock() && db.lastEntryContext.blockNum < blockNum { + return types.BlockSeal{}, types.ErrFuture + } + evtHash, iter, err := db.findLogInfo(blockNum, logIdx) if err != nil { - return 0, err // may be ErrConflict if the block does not have as many logs + return types.BlockSeal{}, err // may be ErrConflict if the block does not have as many logs } db.log.Trace("Found initiatingEvent", "blockNum", blockNum, "logIdx", logIdx, "hash", evtHash) // Found the requested block and log index, check if the hash matches if evtHash != logHash { - return 0, fmt.Errorf("payload hash mismatch: expected %s, got %s", logHash, evtHash) + return types.BlockSeal{}, fmt.Errorf("payload hash mismatch: expected %s, got %s %w", logHash, evtHash, types.ErrConflict) } - return iter.NextIndex(), nil + // Now find the block seal after the log, to identify where the log was included in. + err = iter.TraverseConditional(func(state IteratorState) error { + _, n, ok := state.SealedBlock() + if !ok { // incomplete block data + return nil + } + if n == blockNum { + return types.ErrStop + } + if n > blockNum { + return types.ErrDataCorruption + } + return nil + }) + if err == nil { + panic("expected iterator to stop with error") + } + if errors.Is(err, types.ErrStop) { + h, n, _ := iter.SealedBlock() + timestamp, _ := iter.SealedTimestamp() + return types.BlockSeal{ + Hash: h, + Number: n, + Timestamp: timestamp, + }, nil + } + return types.BlockSeal{}, err } func (db *DB) findLogInfo(blockNum uint64, logIdx uint32) (common.Hash, Iterator, error) { if blockNum == 0 { - return common.Hash{}, nil, ErrConflict // no logs in block 0 + return common.Hash{}, nil, types.ErrConflict // no logs in block 0 } // blockNum-1, such that we find a log that came after the parent num-1 was sealed. // logIdx, such that all entries before logIdx can be skipped, but logIdx itself is still readable. iter, err := db.newIteratorAt(blockNum-1, logIdx) - if errors.Is(err, ErrFuture) { + if errors.Is(err, types.ErrFuture) { db.log.Trace("Could not find log yet", "blockNum", blockNum, "logIdx", logIdx) return common.Hash{}, nil, err } else if err != nil { @@ -245,7 +345,7 @@ func (db *DB) findLogInfo(blockNum uint64, logIdx uint32) (common.Hash, Iterator } else if x < blockNum-1 { panic(fmt.Errorf("bug in newIteratorAt, expected to have found parent block %d but got %d", blockNum-1, x)) } else if x > blockNum-1 { - return common.Hash{}, nil, fmt.Errorf("log does not exist, found next block already: %w", ErrConflict) + return common.Hash{}, nil, fmt.Errorf("log does not exist, found next block already: %w", types.ErrConflict) } logHash, x, ok := iter.InitMessage() if !ok { @@ -266,28 +366,25 @@ func (db *DB) newIteratorAt(blockNum uint64, logIndex uint32) (*iterator, error) searchCheckpointIndex, err := db.searchCheckpoint(blockNum, logIndex) if errors.Is(err, io.EOF) { // Did not find a checkpoint to start reading from so the log cannot be present. - return nil, ErrFuture + return nil, types.ErrFuture } else if err != nil { return nil, err } // The iterator did not consume the checkpoint yet, it's positioned right at it. // So we can call NextBlock() and get the checkpoint itself as first entry. iter := db.newIterator(searchCheckpointIndex) - if err != nil { - return nil, err - } - iter.current.need.Add(entrydb.FlagCanonicalHash) + iter.current.need.Add(FlagCanonicalHash) defer func() { db.m.RecordDBSearchEntriesRead(iter.entriesRead) }() // First walk up to the block that we are sealed up to (incl.) for { - if _, n, _ := iter.SealedBlock(); n == blockNum { // we may already have it exactly + if _, n, ok := iter.SealedBlock(); ok && n == blockNum { // we may already have it exactly break } - if err := iter.NextBlock(); errors.Is(err, ErrFuture) { + if err := iter.NextBlock(); errors.Is(err, types.ErrFuture) { db.log.Trace("ran out of data, could not find block", "nextIndex", iter.NextIndex(), "target", blockNum) - return nil, ErrFuture + return nil, types.ErrFuture } else if err != nil { db.log.Error("failed to read next block", "nextIndex", iter.NextIndex(), "target", blockNum, "err", err) return nil, err @@ -301,7 +398,7 @@ func (db *DB) newIteratorAt(blockNum uint64, logIndex uint32) (*iterator, error) continue } if num != blockNum { // block does not contain - return nil, fmt.Errorf("looking for %d, but already at %d: %w", blockNum, num, ErrConflict) + return nil, fmt.Errorf("looking for %d, but already at %d: %w", blockNum, num, types.ErrConflict) } break } @@ -310,7 +407,7 @@ func (db *DB) newIteratorAt(blockNum uint64, logIndex uint32) (*iterator, error) // so two logs before quiting (and not 3 to then quit after). for iter.current.logsSince < logIndex { if err := iter.NextInitMsg(); err == io.EOF { - return nil, ErrFuture + return nil, types.ErrFuture } else if err != nil { return nil, err } @@ -320,7 +417,7 @@ func (db *DB) newIteratorAt(blockNum uint64, logIndex uint32) (*iterator, error) } if num > blockNum { // we overshot, the block did not contain as many seen log events as requested - return nil, ErrConflict + return nil, types.ErrConflict } _, idx, ok := iter.InitMessage() if !ok { @@ -354,7 +451,7 @@ func (db *DB) newIterator(index entrydb.EntryIdx) *iterator { // Returns the index of the searchCheckpoint to begin reading from or an error. func (db *DB) searchCheckpoint(sealedBlockNum uint64, logsSince uint32) (entrydb.EntryIdx, error) { if db.lastEntryContext.nextEntryIndex == 0 { - return 0, ErrFuture // empty DB, everything is in the future + return 0, types.ErrFuture // empty DB, everything is in the future } n := (db.lastEntryIdx() / searchCheckpointFrequency) + 1 // Define: x is the array of known checkpoints @@ -391,7 +488,7 @@ func (db *DB) searchCheckpoint(sealedBlockNum uint64, logsSince uint32) (entrydb if checkpoint.blockNum > sealedBlockNum || (checkpoint.blockNum == sealedBlockNum && checkpoint.logsSince > logsSince) { return 0, fmt.Errorf("missing data, earliest search checkpoint is %d with %d logs, cannot find something before or at %d with %d logs: %w", - checkpoint.blockNum, checkpoint.logsSince, sealedBlockNum, logsSince, ErrSkipped) + checkpoint.blockNum, checkpoint.logsSince, sealedBlockNum, logsSince, types.ErrSkipped) } return result, nil } diff --git a/op-supervisor/supervisor/backend/db/logs/db_invariants_test.go b/op-supervisor/supervisor/backend/db/logs/db_invariants_test.go index 04c004f3d0966..71da9b3ac6f10 100644 --- a/op-supervisor/supervisor/backend/db/logs/db_invariants_test.go +++ b/op-supervisor/supervisor/backend/db/logs/db_invariants_test.go @@ -7,12 +7,11 @@ import ( "os" "testing" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" "github.com/stretchr/testify/require" ) type statInvariant func(stat os.FileInfo, m *stubMetrics) error -type entryInvariant func(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error +type entryInvariant func(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error // checkDBInvariants reads the database log directly and asserts a set of invariants on the data. func checkDBInvariants(t *testing.T, dbPath string, m *stubMetrics) { @@ -30,11 +29,11 @@ func checkDBInvariants(t *testing.T, dbPath string, m *stubMetrics) { // Read all entries as binary blobs file, err := os.OpenFile(dbPath, os.O_RDONLY, 0o644) require.NoError(t, err) - entries := make([]entrydb.Entry, stat.Size()/entrydb.EntrySize) + entries := make([]Entry, stat.Size()/EntrySize) for i := range entries { n, err := io.ReadFull(file, entries[i][:]) require.NoErrorf(t, err, "failed to read entry %v", i) - require.EqualValuesf(t, entrydb.EntrySize, n, "read wrong length for entry %v", i) + require.EqualValuesf(t, EntrySize, n, "read wrong length for entry %v", i) } entryInvariants := []entryInvariant{ @@ -56,7 +55,7 @@ func checkDBInvariants(t *testing.T, dbPath string, m *stubMetrics) { } } -func fmtEntries(entries []entrydb.Entry) string { +func fmtEntries(entries []Entry) string { out := "" for i, entry := range entries { out += fmt.Sprintf("%v: %x\n", i, entry) @@ -66,58 +65,58 @@ func fmtEntries(entries []entrydb.Entry) string { func invariantFileSizeMultipleOfEntrySize(stat os.FileInfo, _ *stubMetrics) error { size := stat.Size() - if size%entrydb.EntrySize != 0 { - return fmt.Errorf("expected file size to be a multiple of entry size (%v) but was %v", entrydb.EntrySize, size) + if size%EntrySize != 0 { + return fmt.Errorf("expected file size to be a multiple of entry size (%v) but was %v", EntrySize, size) } return nil } func invariantFileSizeMatchesEntryCountMetric(stat os.FileInfo, m *stubMetrics) error { size := stat.Size() - if m.entryCount*entrydb.EntrySize != size { - return fmt.Errorf("expected file size to be entryCount (%v) * entrySize (%v) = %v but was %v", m.entryCount, entrydb.EntrySize, m.entryCount*entrydb.EntrySize, size) + if m.entryCount*EntrySize != size { + return fmt.Errorf("expected file size to be entryCount (%v) * entrySize (%v) = %v but was %v", m.entryCount, EntrySize, m.entryCount*EntrySize, size) } return nil } -func invariantSearchCheckpointAtEverySearchCheckpointFrequency(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { - if entryIdx%searchCheckpointFrequency == 0 && entry.Type() != entrydb.TypeSearchCheckpoint { +func invariantSearchCheckpointAtEverySearchCheckpointFrequency(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error { + if entryIdx%searchCheckpointFrequency == 0 && entry.Type() != TypeSearchCheckpoint { return fmt.Errorf("should have search checkpoints every %v entries but entry %v was %x", searchCheckpointFrequency, entryIdx, entry) } return nil } -func invariantCanonicalHashOrCheckpointAfterEverySearchCheckpoint(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { - if entry.Type() != entrydb.TypeSearchCheckpoint { +func invariantCanonicalHashOrCheckpointAfterEverySearchCheckpoint(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error { + if entry.Type() != TypeSearchCheckpoint { return nil } if entryIdx+1 >= len(entries) { return fmt.Errorf("expected canonical hash or checkpoint after search checkpoint at entry %v but no further entries found", entryIdx) } nextEntry := entries[entryIdx+1] - if nextEntry.Type() != entrydb.TypeCanonicalHash && nextEntry.Type() != entrydb.TypeSearchCheckpoint { + if nextEntry.Type() != TypeCanonicalHash && nextEntry.Type() != TypeSearchCheckpoint { return fmt.Errorf("expected canonical hash or checkpoint after search checkpoint at entry %v but got %x", entryIdx, nextEntry) } return nil } // invariantSearchCheckpointBeforeEveryCanonicalHash ensures we don't have extra canonical-hash entries -func invariantSearchCheckpointBeforeEveryCanonicalHash(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { - if entry.Type() != entrydb.TypeCanonicalHash { +func invariantSearchCheckpointBeforeEveryCanonicalHash(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error { + if entry.Type() != TypeCanonicalHash { return nil } if entryIdx == 0 { return fmt.Errorf("expected search checkpoint before canonical hash at entry %v but no previous entries present", entryIdx) } prevEntry := entries[entryIdx-1] - if prevEntry.Type() != entrydb.TypeSearchCheckpoint { + if prevEntry.Type() != TypeSearchCheckpoint { return fmt.Errorf("expected search checkpoint before canonical hash at entry %v but got %x", entryIdx, prevEntry) } return nil } -func invariantExecLinkAfterInitEventWithFlagSet(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { - if entry.Type() != entrydb.TypeInitiatingEvent { +func invariantExecLinkAfterInitEventWithFlagSet(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error { + if entry.Type() != TypeInitiatingEvent { return nil } hasExecMessage := entry[1]&eventFlagHasExecutingMessage != 0 @@ -131,14 +130,14 @@ func invariantExecLinkAfterInitEventWithFlagSet(entryIdx int, entry entrydb.Entr if len(entries) <= linkIdx { return fmt.Errorf("expected executing link after initiating event with exec msg flag set at entry %v but there were no more events", entryIdx) } - if entries[linkIdx].Type() != entrydb.TypeExecutingLink { + if entries[linkIdx].Type() != TypeExecutingLink { return fmt.Errorf("expected executing link at idx %v after initiating event with exec msg flag set at entry %v but got type %v", linkIdx, entryIdx, entries[linkIdx][0]) } return nil } -func invariantExecLinkOnlyAfterInitiatingEventWithFlagSet(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { - if entry.Type() != entrydb.TypeExecutingLink { +func invariantExecLinkOnlyAfterInitiatingEventWithFlagSet(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error { + if entry.Type() != TypeExecutingLink { return nil } if entryIdx == 0 { @@ -152,7 +151,7 @@ func invariantExecLinkOnlyAfterInitiatingEventWithFlagSet(entryIdx int, entry en return fmt.Errorf("found executing link without a preceding initiating event at entry %v", entryIdx) } initEntry := entries[initIdx] - if initEntry.Type() != entrydb.TypeInitiatingEvent { + if initEntry.Type() != TypeInitiatingEvent { return fmt.Errorf("expected initiating event at entry %v prior to executing link at %v but got %x", initIdx, entryIdx, initEntry[0]) } flags := initEntry[1] @@ -162,8 +161,8 @@ func invariantExecLinkOnlyAfterInitiatingEventWithFlagSet(entryIdx int, entry en return nil } -func invariantExecCheckAfterExecLink(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { - if entry.Type() != entrydb.TypeExecutingLink { +func invariantExecCheckAfterExecLink(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error { + if entry.Type() != TypeExecutingLink { return nil } checkIdx := entryIdx + 1 @@ -174,14 +173,14 @@ func invariantExecCheckAfterExecLink(entryIdx int, entry entrydb.Entry, entries return fmt.Errorf("expected executing link at %v to be followed by executing check at %v but ran out of entries", entryIdx, checkIdx) } checkEntry := entries[checkIdx] - if checkEntry.Type() != entrydb.TypeExecutingCheck { + if checkEntry.Type() != TypeExecutingCheck { return fmt.Errorf("expected executing link at %v to be followed by executing check at %v but got type %v", entryIdx, checkIdx, checkEntry[0]) } return nil } -func invariantExecCheckOnlyAfterExecLink(entryIdx int, entry entrydb.Entry, entries []entrydb.Entry, m *stubMetrics) error { - if entry.Type() != entrydb.TypeExecutingCheck { +func invariantExecCheckOnlyAfterExecLink(entryIdx int, entry Entry, entries []Entry, m *stubMetrics) error { + if entry.Type() != TypeExecutingCheck { return nil } if entryIdx == 0 { @@ -195,7 +194,7 @@ func invariantExecCheckOnlyAfterExecLink(entryIdx int, entry entrydb.Entry, entr return fmt.Errorf("found executing link without a preceding initiating event at entry %v", entryIdx) } linkEntry := entries[linkIdx] - if linkEntry.Type() != entrydb.TypeExecutingLink { + if linkEntry.Type() != TypeExecutingLink { return fmt.Errorf("expected executing link at entry %v prior to executing check at %v but got %x", linkIdx, entryIdx, linkEntry[0]) } return nil diff --git a/op-supervisor/supervisor/backend/db/logs/db_test.go b/op-supervisor/supervisor/backend/db/logs/db_test.go index 31067b05808d5..5e7c22c899398 100644 --- a/op-supervisor/supervisor/backend/db/logs/db_test.go +++ b/op-supervisor/supervisor/backend/db/logs/db_test.go @@ -2,7 +2,6 @@ package logs import ( "encoding/binary" - "io" "io/fs" "os" "path/filepath" @@ -90,7 +89,8 @@ func TestLatestSealedBlockNum(t *testing.T) { require.False(t, ok, "empty db expected") require.Zero(t, n) idx, err := db.searchCheckpoint(0, 0) - require.ErrorIs(t, err, ErrFuture, "no checkpoint in empty db") + require.ErrorIs(t, err, types.ErrFuture, "no checkpoint in empty db") + require.ErrorIs(t, err, types.ErrFuture, "no checkpoint in empty db") require.Zero(t, idx) }) }) @@ -107,6 +107,14 @@ func TestLatestSealedBlockNum(t *testing.T) { idx, err := db.searchCheckpoint(0, 0) require.NoError(t, err) require.Zero(t, idx, "genesis block as checkpoint 0") + + // Test if we can open the genesis block + ref, logCount, execMsgs, err := db.OpenBlock(0) + require.NoError(t, err) + require.Empty(t, execMsgs) + require.Zero(t, logCount) + require.Equal(t, genesis, ref.ID()) + require.Equal(t, uint64(5000), ref.Time) }) }) t.Run("Later genesis case", func(t *testing.T) { @@ -123,7 +131,16 @@ func TestLatestSealedBlockNum(t *testing.T) { require.NoError(t, err) require.Zero(t, idx, "anchor block as checkpoint 0") _, err = db.searchCheckpoint(0, 0) - require.ErrorIs(t, err, ErrSkipped, "no checkpoint before genesis") + require.ErrorIs(t, err, types.ErrSkipped, "no checkpoint before genesis") + require.ErrorIs(t, err, types.ErrSkipped, "no checkpoint before genesis") + + // Test if we can open the starting block + _, _, _, err = db.OpenBlock(genesis.Number) + // no data to find the parent-hash. + // OpenBlock cannot start from the first entry, when not 0. + // To start at a non-zero block, index the seal of the parent-block block before it, + // and then that parent-hash will be available. + require.ErrorIs(t, err, types.ErrSkipped) }) }) t.Run("Block 1 case", func(t *testing.T) { @@ -141,6 +158,22 @@ func TestLatestSealedBlockNum(t *testing.T) { idx, err := db.searchCheckpoint(block1.Number, 0) require.NoError(t, err) require.Equal(t, entrydb.EntryIdx(0), idx, "checkpoint 0 still for block 1") + + // Test if we can open the starting block + ref, logCount, execMsgs, err := db.OpenBlock(genesis.Number) + require.NoError(t, err) + require.Empty(t, execMsgs) + require.Zero(t, logCount) + require.Equal(t, genesis, ref.ID()) + require.Equal(t, uint64(5000), ref.Time) + + // Test if we can open the first block after genesis + ref, logCount, execMsgs, err = db.OpenBlock(block1.Number) + require.NoError(t, err) + require.Empty(t, execMsgs) + require.Zero(t, logCount) + require.Equal(t, block1, ref.ID()) + require.Equal(t, uint64(5001), ref.Time) }) }) t.Run("Using checkpoint case", func(t *testing.T) { @@ -150,7 +183,7 @@ func TestLatestSealedBlockNum(t *testing.T) { require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis") for i := 1; i <= 260; i++ { id := eth.BlockID{Hash: createHash(i), Number: uint64(i)} - require.NoError(t, db.SealBlock(createHash(i-1), id, 5001), "seal block %d", i) + require.NoError(t, db.SealBlock(createHash(i-1), id, 5000+uint64(i)), "seal block %d", i) } }, func(t *testing.T, db *DB, m *stubMetrics) { @@ -163,6 +196,14 @@ func TestLatestSealedBlockNum(t *testing.T) { // It costs 2 entries per block, so if we add more than 1 checkpoint worth of blocks, // then we get to checkpoint 2 require.Equal(t, entrydb.EntryIdx(searchCheckpointFrequency*2), idx, "checkpoint 1 reached") + + // Test if we can open the block + ref, logCount, execMsgs, err := db.OpenBlock(n) + require.NoError(t, err) + require.Empty(t, execMsgs) + require.Zero(t, logCount) + require.Equal(t, createHash(int(n)), ref.Hash) + require.Equal(t, uint64(5000)+n, ref.Time) }) }) } @@ -175,7 +216,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { genesis := eth.BlockID{Hash: createHash(15), Number: 0} err := db.AddLog(createHash(1), genesis, 0, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) }) }) @@ -190,6 +232,12 @@ func TestAddLog(t *testing.T) { }, func(t *testing.T, db *DB, m *stubMetrics) { requireContains(t, db, 16, 0, createHash(1)) + + ref, logCount, execMsgs, err := db.OpenBlock(16) + require.NoError(t, err) + require.Empty(t, execMsgs) + require.Equal(t, uint32(1), logCount) + require.Equal(t, eth.BlockRef{Hash: createHash(16), Number: 16, ParentHash: createHash(15), Time: 5001}, ref) }) }) @@ -219,6 +267,18 @@ func TestAddLog(t *testing.T) { requireContains(t, db, 16, 0, createHash(1)) requireContains(t, db, 16, 1, createHash(2)) requireContains(t, db, 16, 2, createHash(3)) + + ref, logCount, execMsgs, err := db.OpenBlock(13) + require.NoError(t, err) + require.Empty(t, execMsgs) + require.Equal(t, uint32(0), logCount) + require.Equal(t, eth.BlockRef{Hash: createHash(13), Number: 13, ParentHash: createHash(12), Time: 5013}, ref) + + ref, logCount, execMsgs, err = db.OpenBlock(16) + require.NoError(t, err) + require.Empty(t, execMsgs) + require.Equal(t, uint32(3), logCount) + require.Equal(t, eth.BlockRef{Hash: createHash(16), Number: 16, ParentHash: createHash(15), Time: 5016}, ref) }) }) @@ -265,7 +325,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { bl14 := eth.BlockID{Hash: createHash(14), Number: 14} err := db.SealBlock(createHash(13), bl14, 5000) - require.ErrorIs(t, err, ErrConflict) + require.ErrorIs(t, err, types.ErrConflict) + require.ErrorIs(t, err, types.ErrConflict) }) }) @@ -282,7 +343,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { onto := eth.BlockID{Hash: createHash(14), Number: 14} err := db.AddLog(createHash(1), onto, 0, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder, "cannot build logs on 14 when 15 is already sealed") + require.ErrorIs(t, err, types.ErrOutOfOrder, "cannot build logs on 14 when 15 is already sealed") + require.ErrorIs(t, err, types.ErrOutOfOrder, "cannot build logs on 14 when 15 is already sealed") }) }) @@ -298,7 +360,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { bl15 := eth.BlockID{Hash: createHash(15), Number: 15} err := db.AddLog(createHash(1), bl15, 0, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder, "already at log index 2") + require.ErrorIs(t, err, types.ErrOutOfOrder, "already at log index 2") + require.ErrorIs(t, err, types.ErrOutOfOrder, "already at log index 2") }) }) @@ -313,7 +376,8 @@ func TestAddLog(t *testing.T) { }, func(t *testing.T, db *DB, m *stubMetrics) { err := db.AddLog(createHash(1), eth.BlockID{Hash: createHash(16), Number: 16}, 0, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) }) }) @@ -329,7 +393,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { bl15 := eth.BlockID{Hash: createHash(15), Number: 15} err := db.AddLog(createHash(1), bl15, 1, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder, "already at log index 2") + require.ErrorIs(t, err, types.ErrOutOfOrder, "already at log index 2") + require.ErrorIs(t, err, types.ErrOutOfOrder, "already at log index 2") }) }) @@ -345,7 +410,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { bl15 := eth.BlockID{Hash: createHash(16), Number: 16} err := db.AddLog(createHash(1), bl15, 2, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) }) }) @@ -360,7 +426,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { bl15 := eth.BlockID{Hash: createHash(15), Number: 15} err := db.AddLog(createHash(1), bl15, 2, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) }) }) @@ -373,7 +440,8 @@ func TestAddLog(t *testing.T) { func(t *testing.T, db *DB, m *stubMetrics) { bl15 := eth.BlockID{Hash: createHash(15), Number: 15} err := db.AddLog(createHash(1), bl15, 5, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) }) }) @@ -394,7 +462,8 @@ func TestAddLog(t *testing.T) { err = db.SealBlock(bl15.Hash, bl16, 5001) require.NoError(t, err) err = db.AddLog(createHash(1), bl16, 1, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) }) }) @@ -513,6 +582,8 @@ func TestAddDependentLog(t *testing.T) { require.NoError(t, db.lastEntryContext.forceBlock(bl15, 5000)) err := db.AddLog(createHash(1), bl15, 0, &execMsg) require.NoError(t, err) + bl16 := eth.BlockID{Hash: createHash(16), Number: 16} + require.NoError(t, db.SealBlock(bl15.Hash, bl16, 5002)) }, func(t *testing.T, db *DB, m *stubMetrics) { requireContains(t, db, 16, 0, createHash(1), execMsg) @@ -533,6 +604,8 @@ func TestAddDependentLog(t *testing.T) { require.Equal(t, m.entryCount, int64(searchCheckpointFrequency+2)) err := db.AddLog(createHash(1), bl16, 0, &execMsg) require.NoError(t, err) + bl17 := eth.BlockID{Hash: createHash(17), Number: 17} + require.NoError(t, db.SealBlock(bl16.Hash, bl17, 5002)) }, func(t *testing.T, db *DB, m *stubMetrics) { requireContains(t, db, 16, 0, createHash(9)) @@ -563,6 +636,8 @@ func TestAddDependentLog(t *testing.T) { // 260 = executing message check require.Equal(t, int64(261), m.entryCount) db.debugTip() + bl16 := eth.BlockID{Hash: createHash(16), Number: 16} + require.NoError(t, db.SealBlock(bl15.Hash, bl16, 5001)) }, func(t *testing.T, db *DB, m *stubMetrics) { requireContains(t, db, 16, 251, createHash(9)) @@ -592,6 +667,8 @@ func TestAddDependentLog(t *testing.T) { // 260 = executing message check db.debugTip() require.Equal(t, int64(261), m.entryCount) + bl16 := eth.BlockID{Hash: createHash(16), Number: 16} + require.NoError(t, db.SealBlock(bl15.Hash, bl16, 5001)) }, func(t *testing.T, db *DB, m *stubMetrics) { requireContains(t, db, 16, 252, createHash(9)) @@ -620,8 +697,8 @@ func TestContains(t *testing.T) { requireContains(t, db, 51, 0, createHash(1)) requireContains(t, db, 51, 1, createHash(3)) requireContains(t, db, 51, 2, createHash(2)) - requireContains(t, db, 53, 0, createHash(1)) - requireContains(t, db, 53, 1, createHash(3)) + requireFuture(t, db, 53, 0, createHash(1)) + requireFuture(t, db, 53, 1, createHash(3)) // 52 was sealed as empty requireConflicts(t, db, 52, 0, createHash(1)) @@ -690,6 +767,14 @@ func TestExecutes(t *testing.T) { // 51 only contained 3 logs, not 4 requireConflicts(t, db, 51, 3, createHash(2)) + + // 51 contains an executing message, and 2 other non-executing logs + ref, logCount, execMsgs, err := db.OpenBlock(51) + require.NoError(t, err) + require.Len(t, execMsgs, 1) + require.Equal(t, &execMsg1, execMsgs[1]) + require.Equal(t, uint32(3), logCount) + require.Equal(t, eth.BlockRef{Hash: createHash(51), Number: 51, ParentHash: createHash(50), Time: 5001}, ref) }) } @@ -698,9 +783,10 @@ func TestGetBlockInfo(t *testing.T) { runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {}, func(t *testing.T, db *DB, m *stubMetrics) { - bl10 := eth.BlockID{Hash: createHash(10), Number: 10} - _, err := db.FindSealedBlock(bl10) - require.ErrorIs(t, err, ErrFuture) + _, err := db.FindSealedBlock(10) + require.ErrorIs(t, err, types.ErrFuture) + _, err = db.FindSealedBlock(10) + require.ErrorIs(t, err, types.ErrFuture) }) }) @@ -714,9 +800,10 @@ func TestGetBlockInfo(t *testing.T) { }, func(t *testing.T, db *DB, m *stubMetrics) { // if the DB starts at 11, then shouldn't find 10 - bl10 := eth.BlockID{Hash: createHash(10), Number: 10} - _, err := db.FindSealedBlock(bl10) - require.ErrorIs(t, err, ErrSkipped) + _, err := db.FindSealedBlock(10) + require.ErrorIs(t, err, types.ErrSkipped) + _, err = db.FindSealedBlock(10) + require.ErrorIs(t, err, types.ErrSkipped) }) }) @@ -727,10 +814,14 @@ func TestGetBlockInfo(t *testing.T) { require.NoError(t, db.SealBlock(common.Hash{}, block, 500)) }, func(t *testing.T, db *DB, m *stubMetrics) { - index, err := db.FindSealedBlock(block) + _, err := db.FindSealedBlock(block.Number) + require.NoError(t, err) + seal, err := db.FindSealedBlock(block.Number) require.NoError(t, err) - require.Equal(t, entrydb.EntryIdx(2), index, - "expecting to continue after search checkpoint that declared the block") + require.Equal(t, block, seal.ID()) + require.Equal(t, uint64(500), seal.Timestamp) + require.Equal(t, block, seal.ID()) + require.Equal(t, uint64(500), seal.Timestamp) }) }) } @@ -755,7 +846,8 @@ func requireConflicts(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logH m, ok := db.m.(*stubMetrics) require.True(t, ok, "Did not get the expected metrics type") _, err := db.Contains(blockNum, logIdx, logHash) - require.ErrorIs(t, err, ErrConflict, "canonical chain must not include this log") + require.ErrorIs(t, err, types.ErrConflict, "canonical chain must not include this log") + require.ErrorIs(t, err, types.ErrConflict, "canonical chain must not include this log") require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency*2), "Should not need to read more than between two checkpoints") } @@ -763,7 +855,8 @@ func requireFuture(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash m, ok := db.m.(*stubMetrics) require.True(t, ok, "Did not get the expected metrics type") _, err := db.Contains(blockNum, logIdx, logHash) - require.ErrorIs(t, err, ErrFuture, "canonical chain does not yet include this log") + require.ErrorIs(t, err, types.ErrFuture, "canonical chain does not yet include this log") + require.ErrorIs(t, err, types.ErrFuture, "canonical chain does not yet include this log") require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency*2), "Should not need to read more than between two checkpoints") } @@ -784,16 +877,16 @@ func requireExecutingMessage(t *testing.T, db *DB, blockNum uint64, logIdx uint3 } func TestRecoverOnCreate(t *testing.T) { - createDb := func(t *testing.T, store *stubEntryStore) (*DB, *stubMetrics, error) { + createDb := func(t *testing.T, store *entrydb.MemEntryStore[EntryType, Entry]) (*DB, *stubMetrics, error) { logger := testlog.Logger(t, log.LvlInfo) m := &stubMetrics{} db, err := NewFromEntryStore(logger, m, store, true) return db, m, err } - storeWithEvents := func(evts ...entrydb.Entry) *stubEntryStore { - store := &stubEntryStore{} - store.entries = append(store.entries, evts...) + storeWithEvents := func(evts ...Entry) *entrydb.MemEntryStore[EntryType, Entry] { + store := &entrydb.MemEntryStore[EntryType, Entry]{} + _ = store.Append(evts...) return store } t.Run("NoTruncateWhenLastEntryIsLogWithNoExecMessageSealed", func(t *testing.T) { @@ -924,9 +1017,11 @@ func TestRewind(t *testing.T) { t.Run("WhenEmpty", func(t *testing.T) { runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {}, func(t *testing.T, db *DB, m *stubMetrics) { - require.ErrorIs(t, db.Rewind(100), ErrFuture) + require.ErrorIs(t, db.Rewind(100), types.ErrFuture) + require.ErrorIs(t, db.Rewind(100), types.ErrFuture) // Genesis is a block to, not present in an empty DB - require.ErrorIs(t, db.Rewind(0), ErrFuture) + require.ErrorIs(t, db.Rewind(0), types.ErrFuture) + require.ErrorIs(t, db.Rewind(0), types.ErrFuture) }) }) @@ -944,14 +1039,15 @@ func TestRewind(t *testing.T) { require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504)) require.NoError(t, db.AddLog(createHash(4), bl52, 0, nil)) // cannot rewind to a block that is not sealed yet - require.ErrorIs(t, db.Rewind(53), ErrFuture) + require.ErrorIs(t, db.Rewind(53), types.ErrFuture) + require.ErrorIs(t, db.Rewind(53), types.ErrFuture) }, func(t *testing.T, db *DB, m *stubMetrics) { requireContains(t, db, 51, 0, createHash(1)) requireContains(t, db, 51, 1, createHash(2)) requireContains(t, db, 52, 0, createHash(3)) // Still have the pending log of unsealed block if the rewind to unknown sealed block fails - requireContains(t, db, 53, 0, createHash(4)) + requireFuture(t, db, 53, 0, createHash(4)) }) }) @@ -963,11 +1059,13 @@ func TestRewind(t *testing.T) { require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil)) require.NoError(t, db.AddLog(createHash(2), bl50, 1, nil)) // cannot go back to an unknown block - require.ErrorIs(t, db.Rewind(25), ErrSkipped) + require.ErrorIs(t, db.Rewind(25), types.ErrSkipped) + require.ErrorIs(t, db.Rewind(25), types.ErrSkipped) }, func(t *testing.T, db *DB, m *stubMetrics) { - requireContains(t, db, 51, 0, createHash(1)) - requireContains(t, db, 51, 0, createHash(1)) + // block 51 is not sealed yet + requireFuture(t, db, 51, 0, createHash(1)) + requireFuture(t, db, 51, 0, createHash(1)) }) }) @@ -1088,17 +1186,20 @@ func TestRewind(t *testing.T) { bl29 := eth.BlockID{Hash: createHash(29), Number: 29} // 29 was deleted err := db.AddLog(createHash(2), bl29, 1, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder, "Cannot add log on removed block") + require.ErrorIs(t, err, types.ErrOutOfOrder, "Cannot add log on removed block") + require.ErrorIs(t, err, types.ErrOutOfOrder, "Cannot add log on removed block") // 15 is older, we have up to 16 bl15 := eth.BlockID{Hash: createHash(15), Number: 15} // try to add a third log to 15 err = db.AddLog(createHash(10), bl15, 2, nil) - require.ErrorIs(t, err, ErrLogOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) + require.ErrorIs(t, err, types.ErrOutOfOrder) bl16 := eth.BlockID{Hash: createHash(16), Number: 16} // try to add a log to 17, on top of 16 err = db.AddLog(createHash(42), bl16, 0, nil) require.NoError(t, err) - requireContains(t, db, 17, 0, createHash(42)) + // not sealed yet + requireFuture(t, db, 17, 0, createHash(42)) }) }) } @@ -1108,7 +1209,7 @@ type stubMetrics struct { entriesReadForSearch int64 } -func (s *stubMetrics) RecordDBEntryCount(count int64) { +func (s *stubMetrics) RecordDBEntryCount(kind string, count int64) { s.entryCount = count } @@ -1118,37 +1219,4 @@ func (s *stubMetrics) RecordDBSearchEntriesRead(count int64) { var _ Metrics = (*stubMetrics)(nil) -type stubEntryStore struct { - entries []entrydb.Entry -} - -func (s *stubEntryStore) Size() int64 { - return int64(len(s.entries)) -} - -func (s *stubEntryStore) LastEntryIdx() entrydb.EntryIdx { - return entrydb.EntryIdx(s.Size() - 1) -} - -func (s *stubEntryStore) Read(idx entrydb.EntryIdx) (entrydb.Entry, error) { - if idx < entrydb.EntryIdx(len(s.entries)) { - return s.entries[idx], nil - } - return entrydb.Entry{}, io.EOF -} - -func (s *stubEntryStore) Append(entries ...entrydb.Entry) error { - s.entries = append(s.entries, entries...) - return nil -} - -func (s *stubEntryStore) Truncate(idx entrydb.EntryIdx) error { - s.entries = s.entries[:min(s.Size()-1, int64(idx+1))] - return nil -} - -func (s *stubEntryStore) Close() error { - return nil -} - -var _ EntryStore = (*stubEntryStore)(nil) +var _ entrydb.EntryStore[EntryType, Entry] = (*entrydb.MemEntryStore[EntryType, Entry])(nil) diff --git a/op-supervisor/supervisor/backend/db/logs/entries.go b/op-supervisor/supervisor/backend/db/logs/entries.go index 431adc99f465d..5c5472c522b74 100644 --- a/op-supervisor/supervisor/backend/db/logs/entries.go +++ b/op-supervisor/supervisor/backend/db/logs/entries.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -27,9 +26,9 @@ func newSearchCheckpoint(blockNum uint64, logsSince uint32, timestamp uint64) se } } -func newSearchCheckpointFromEntry(data entrydb.Entry) (searchCheckpoint, error) { - if data.Type() != entrydb.TypeSearchCheckpoint { - return searchCheckpoint{}, fmt.Errorf("%w: attempting to decode search checkpoint but was type %s", ErrDataCorruption, data.Type()) +func newSearchCheckpointFromEntry(data Entry) (searchCheckpoint, error) { + if data.Type() != TypeSearchCheckpoint { + return searchCheckpoint{}, fmt.Errorf("%w: attempting to decode search checkpoint but was type %s", types.ErrDataCorruption, data.Type()) } return searchCheckpoint{ blockNum: binary.LittleEndian.Uint64(data[1:9]), @@ -40,9 +39,9 @@ func newSearchCheckpointFromEntry(data entrydb.Entry) (searchCheckpoint, error) // encode creates a checkpoint entry // type 0: "search checkpoint" = 21 bytes -func (s searchCheckpoint) encode() entrydb.Entry { - var data entrydb.Entry - data[0] = uint8(entrydb.TypeSearchCheckpoint) +func (s searchCheckpoint) encode() Entry { + var data Entry + data[0] = uint8(TypeSearchCheckpoint) binary.LittleEndian.PutUint64(data[1:9], s.blockNum) binary.LittleEndian.PutUint32(data[9:13], s.logsSince) binary.LittleEndian.PutUint64(data[13:21], s.timestamp) @@ -57,16 +56,16 @@ func newCanonicalHash(hash common.Hash) canonicalHash { return canonicalHash{hash: hash} } -func newCanonicalHashFromEntry(data entrydb.Entry) (canonicalHash, error) { - if data.Type() != entrydb.TypeCanonicalHash { - return canonicalHash{}, fmt.Errorf("%w: attempting to decode canonical hash but was type %s", ErrDataCorruption, data.Type()) +func newCanonicalHashFromEntry(data Entry) (canonicalHash, error) { + if data.Type() != TypeCanonicalHash { + return canonicalHash{}, fmt.Errorf("%w: attempting to decode canonical hash but was type %s", types.ErrDataCorruption, data.Type()) } return newCanonicalHash(common.Hash(data[1:33])), nil } -func (c canonicalHash) encode() entrydb.Entry { - var entry entrydb.Entry - entry[0] = uint8(entrydb.TypeCanonicalHash) +func (c canonicalHash) encode() Entry { + var entry Entry + entry[0] = uint8(TypeCanonicalHash) copy(entry[1:33], c.hash[:]) return entry } @@ -76,9 +75,9 @@ type initiatingEvent struct { logHash common.Hash } -func newInitiatingEventFromEntry(data entrydb.Entry) (initiatingEvent, error) { - if data.Type() != entrydb.TypeInitiatingEvent { - return initiatingEvent{}, fmt.Errorf("%w: attempting to decode initiating event but was type %s", ErrDataCorruption, data.Type()) +func newInitiatingEventFromEntry(data Entry) (initiatingEvent, error) { + if data.Type() != TypeInitiatingEvent { + return initiatingEvent{}, fmt.Errorf("%w: attempting to decode initiating event but was type %s", types.ErrDataCorruption, data.Type()) } flags := data[1] return initiatingEvent{ @@ -96,9 +95,9 @@ func newInitiatingEvent(logHash common.Hash, hasExecMsg bool) initiatingEvent { // encode creates an initiating event entry // type 2: "initiating event" = 22 bytes -func (i initiatingEvent) encode() entrydb.Entry { - var data entrydb.Entry - data[0] = uint8(entrydb.TypeInitiatingEvent) +func (i initiatingEvent) encode() Entry { + var data Entry + data[0] = uint8(TypeInitiatingEvent) flags := byte(0) if i.hasExecMsg { flags = flags | eventFlagHasExecutingMessage @@ -109,7 +108,7 @@ func (i initiatingEvent) encode() entrydb.Entry { } type executingLink struct { - chain uint32 + chain uint32 // chain index, not a chain ID blockNum uint64 logIdx uint32 timestamp uint64 @@ -120,16 +119,16 @@ func newExecutingLink(msg types.ExecutingMessage) (executingLink, error) { return executingLink{}, fmt.Errorf("log idx is too large (%v)", msg.LogIdx) } return executingLink{ - chain: msg.Chain, + chain: uint32(msg.Chain), blockNum: msg.BlockNum, logIdx: msg.LogIdx, timestamp: msg.Timestamp, }, nil } -func newExecutingLinkFromEntry(data entrydb.Entry) (executingLink, error) { - if data.Type() != entrydb.TypeExecutingLink { - return executingLink{}, fmt.Errorf("%w: attempting to decode executing link but was type %s", ErrDataCorruption, data.Type()) +func newExecutingLinkFromEntry(data Entry) (executingLink, error) { + if data.Type() != TypeExecutingLink { + return executingLink{}, fmt.Errorf("%w: attempting to decode executing link but was type %s", types.ErrDataCorruption, data.Type()) } timestamp := binary.LittleEndian.Uint64(data[16:24]) return executingLink{ @@ -142,9 +141,9 @@ func newExecutingLinkFromEntry(data entrydb.Entry) (executingLink, error) { // encode creates an executing link entry // type 3: "executing link" = 24 bytes -func (e executingLink) encode() entrydb.Entry { - var entry entrydb.Entry - entry[0] = uint8(entrydb.TypeExecutingLink) +func (e executingLink) encode() Entry { + var entry Entry + entry[0] = uint8(TypeExecutingLink) binary.LittleEndian.PutUint32(entry[1:5], e.chain) binary.LittleEndian.PutUint64(entry[5:13], e.blockNum) @@ -164,18 +163,18 @@ func newExecutingCheck(hash common.Hash) executingCheck { return executingCheck{hash: hash} } -func newExecutingCheckFromEntry(data entrydb.Entry) (executingCheck, error) { - if data.Type() != entrydb.TypeExecutingCheck { - return executingCheck{}, fmt.Errorf("%w: attempting to decode executing check but was type %s", ErrDataCorruption, data.Type()) +func newExecutingCheckFromEntry(data Entry) (executingCheck, error) { + if data.Type() != TypeExecutingCheck { + return executingCheck{}, fmt.Errorf("%w: attempting to decode executing check but was type %s", types.ErrDataCorruption, data.Type()) } return newExecutingCheck(common.Hash(data[1:33])), nil } // encode creates an executing check entry // type 4: "executing check" = 33 bytes -func (e executingCheck) encode() entrydb.Entry { - var entry entrydb.Entry - entry[0] = uint8(entrydb.TypeExecutingCheck) +func (e executingCheck) encode() Entry { + var entry Entry + entry[0] = uint8(TypeExecutingCheck) copy(entry[1:33], e.hash[:]) return entry } @@ -184,8 +183,8 @@ type paddingEntry struct{} // encoding of the padding entry // type 5: "padding" = 34 bytes -func (e paddingEntry) encode() entrydb.Entry { - var entry entrydb.Entry - entry[0] = uint8(entrydb.TypePadding) +func (e paddingEntry) encode() Entry { + var entry Entry + entry[0] = uint8(TypePadding) return entry } diff --git a/op-supervisor/supervisor/backend/db/logs/entry.go b/op-supervisor/supervisor/backend/db/logs/entry.go new file mode 100644 index 0000000000000..dfbe106d07932 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/logs/entry.go @@ -0,0 +1,98 @@ +package logs + +import ( + "fmt" + "io" + "strings" +) + +type EntryObj interface { + encode() Entry +} + +const EntrySize = 34 + +type Entry [EntrySize]byte + +func (e Entry) Type() EntryType { + return EntryType(e[0]) +} + +type EntryBinary struct{} + +func (EntryBinary) Append(dest []byte, e *Entry) []byte { + return append(dest, e[:]...) +} + +func (EntryBinary) ReadAt(dest *Entry, r io.ReaderAt, at int64) (n int, err error) { + return r.ReadAt(dest[:], at) +} + +func (EntryBinary) EntrySize() int { + return EntrySize +} + +type EntryTypeFlag uint8 + +const ( + FlagSearchCheckpoint EntryTypeFlag = 1 << TypeSearchCheckpoint + FlagCanonicalHash EntryTypeFlag = 1 << TypeCanonicalHash + FlagInitiatingEvent EntryTypeFlag = 1 << TypeInitiatingEvent + FlagExecutingLink EntryTypeFlag = 1 << TypeExecutingLink + FlagExecutingCheck EntryTypeFlag = 1 << TypeExecutingCheck + FlagPadding EntryTypeFlag = 1 << TypePadding + // for additional padding + FlagPadding2 EntryTypeFlag = FlagPadding << 1 +) + +func (x EntryTypeFlag) String() string { + var out []string + for i := EntryTypeFlag(1); i != 0; i <<= 1 { // iterate to bitmask + if x.Any(i) { + out = append(out, i.String()) + } + } + return strings.Join(out, "|") +} + +func (x EntryTypeFlag) Any(v EntryTypeFlag) bool { + return x&v != 0 +} + +func (x *EntryTypeFlag) Add(v EntryTypeFlag) { + *x = *x | v +} + +func (x *EntryTypeFlag) Remove(v EntryTypeFlag) { + *x = *x &^ v +} + +type EntryType uint8 + +const ( + TypeSearchCheckpoint EntryType = iota + TypeCanonicalHash + TypeInitiatingEvent + TypeExecutingLink + TypeExecutingCheck + TypePadding +) + +func (x EntryType) String() string { + switch x { + case TypeSearchCheckpoint: + return "searchCheckpoint" + case TypeCanonicalHash: + return "canonicalHash" + case TypeInitiatingEvent: + return "initiatingEvent" + case TypeExecutingLink: + return "executingLink" + case TypeExecutingCheck: + return "executingCheck" + case TypePadding: + return "padding" + default: + return fmt.Sprintf("unknown-%d", uint8(x)) + } +} diff --git a/op-supervisor/supervisor/backend/db/logs/iterator.go b/op-supervisor/supervisor/backend/db/logs/iterator.go index f9e65c41e890f..c107a2a9fb862 100644 --- a/op-supervisor/supervisor/backend/db/logs/iterator.go +++ b/op-supervisor/supervisor/backend/db/logs/iterator.go @@ -8,14 +8,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) type IteratorState interface { NextIndex() entrydb.EntryIdx - HeadPointer() (heads.HeadPointer, error) SealedBlock() (hash common.Hash, num uint64, ok bool) + SealedTimestamp() (timestamp uint64, ok bool) InitMessage() (hash common.Hash, logIndex uint32, ok bool) ExecMessage() *types.ExecutingMessage } @@ -42,7 +41,7 @@ type traverseConditionalFn func(state IteratorState) error func (i *iterator) End() error { for { _, err := i.next() - if errors.Is(err, ErrFuture) { + if errors.Is(err, types.ErrFuture) { return nil } else if err != nil { return err @@ -50,7 +49,7 @@ func (i *iterator) End() error { } } -// NextInitMsg returns the next initiating message in the iterator. +// NextInitMsg advances the iterator until it reads the next Initiating Message into the current state. // It scans forward until it finds and fully reads an initiating event, skipping any blocks. func (i *iterator) NextInitMsg() error { seenLog := false @@ -59,7 +58,7 @@ func (i *iterator) NextInitMsg() error { if err != nil { return err } - if typ == entrydb.TypeInitiatingEvent { + if typ == TypeInitiatingEvent { seenLog = true } if !i.current.hasCompleteBlock() { @@ -74,9 +73,8 @@ func (i *iterator) NextInitMsg() error { } } -// NextExecMsg returns the next executing message in the iterator. +// NextExecMsg advances the iterator until it reads the next Executing Message into the current state. // It scans forward until it finds and fully reads an initiating event, skipping any blocks. -// This does not stay at the executing message of the current initiating message, if there is any. func (i *iterator) NextExecMsg() error { for { err := i.NextInitMsg() @@ -89,7 +87,7 @@ func (i *iterator) NextExecMsg() error { } } -// NextBlock returns the next block in the iterator. +// NextBlock advances the iterator until it reads the next block into the current state. // It scans forward until it finds and fully reads a block, skipping any events. func (i *iterator) NextBlock() error { seenBlock := false @@ -98,7 +96,7 @@ func (i *iterator) NextBlock() error { if err != nil { return err } - if typ == entrydb.TypeSearchCheckpoint { + if typ == TypeSearchCheckpoint { seenBlock = true } if !i.current.hasCompleteBlock() { @@ -130,12 +128,12 @@ func (i *iterator) TraverseConditional(fn traverseConditionalFn) error { } // Read and apply the next entry. -func (i *iterator) next() (entrydb.EntryType, error) { +func (i *iterator) next() (EntryType, error) { index := i.current.nextEntryIndex entry, err := i.db.store.Read(index) if err != nil { if errors.Is(err, io.EOF) { - return 0, ErrFuture + return 0, types.ErrFuture } return 0, fmt.Errorf("failed to read entry %d: %w", index, err) } @@ -157,6 +155,11 @@ func (i *iterator) SealedBlock() (hash common.Hash, num uint64, ok bool) { return i.current.SealedBlock() } +// SealedTimestamp returns the timestamp of SealedBlock +func (i *iterator) SealedTimestamp() (timestamp uint64, ok bool) { + return i.current.SealedTimestamp() +} + // InitMessage returns the current initiating message, if any is available. func (i *iterator) InitMessage() (hash common.Hash, logIndex uint32, ok bool) { return i.current.InitMessage() @@ -166,7 +169,3 @@ func (i *iterator) InitMessage() (hash common.Hash, logIndex uint32, ok bool) { func (i *iterator) ExecMessage() *types.ExecutingMessage { return i.current.ExecMessage() } - -func (i *iterator) HeadPointer() (heads.HeadPointer, error) { - return i.current.HeadPointer() -} diff --git a/op-supervisor/supervisor/backend/db/logs/state.go b/op-supervisor/supervisor/backend/db/logs/state.go index df63f96e35997..3c90102b1ad0a 100644 --- a/op-supervisor/supervisor/backend/db/logs/state.go +++ b/op-supervisor/supervisor/backend/db/logs/state.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -73,18 +72,14 @@ type logContext struct { // then we know an executing message is still coming. execMsg *types.ExecutingMessage - need entrydb.EntryTypeFlag + need EntryTypeFlag // buffer of entries not yet in the DB. // This is generated as objects are applied. // E.g. you can build multiple hypothetical blocks with log events on top of the state, // before flushing the entries to a DB. // However, no entries can be read from the DB while objects are being applied. - out []entrydb.Entry -} - -type EntryObj interface { - encode() entrydb.Entry + out []Entry } func (l *logContext) NextIndex() entrydb.EntryIdx { @@ -99,12 +94,19 @@ func (l *logContext) SealedBlock() (hash common.Hash, num uint64, ok bool) { return l.blockHash, l.blockNum, true } +func (l *logContext) SealedTimestamp() (timestamp uint64, ok bool) { + if !l.hasCompleteBlock() { + return 0, false + } + return l.timestamp, true +} + func (l *logContext) hasCompleteBlock() bool { - return !l.need.Any(entrydb.FlagCanonicalHash) + return !l.need.Any(FlagCanonicalHash) } func (l *logContext) hasIncompleteLog() bool { - return l.need.Any(entrydb.FlagInitiatingEvent | entrydb.FlagExecutingLink | entrydb.FlagExecutingCheck) + return l.need.Any(FlagInitiatingEvent | FlagExecutingLink | FlagExecutingCheck) } func (l *logContext) hasReadableLog() bool { @@ -127,20 +129,8 @@ func (l *logContext) ExecMessage() *types.ExecutingMessage { return nil } -func (l *logContext) HeadPointer() (heads.HeadPointer, error) { - if l.need != 0 { - return heads.HeadPointer{}, errors.New("cannot provide head pointer while state is incomplete") - } - return heads.HeadPointer{ - LastSealedBlockHash: l.blockHash, - LastSealedBlockNum: l.blockNum, - LastSealedTimestamp: l.timestamp, - LogsSince: l.logsSince, - }, nil -} - // ApplyEntry applies an entry on top of the current state. -func (l *logContext) ApplyEntry(entry entrydb.Entry) error { +func (l *logContext) ApplyEntry(entry Entry) error { // Wrap processEntry to add common useful error message info err := l.processEntry(entry) if err != nil { @@ -152,28 +142,28 @@ func (l *logContext) ApplyEntry(entry entrydb.Entry) error { // processEntry decodes and applies an entry to the state. // Entries may not be applied if we are in the process of generating entries from objects. // These outputs need to be flushed before inputs can be accepted. -func (l *logContext) processEntry(entry entrydb.Entry) error { +func (l *logContext) processEntry(entry Entry) error { if len(l.out) != 0 { panic("can only apply without appending if the state is still empty") } switch entry.Type() { - case entrydb.TypeSearchCheckpoint: + case TypeSearchCheckpoint: current, err := newSearchCheckpointFromEntry(entry) if err != nil { return err } l.blockNum = current.blockNum l.blockHash = common.Hash{} - l.logsSince = current.logsSince // TODO this is bumping the logsSince? + l.logsSince = current.logsSince l.timestamp = current.timestamp - l.need.Add(entrydb.FlagCanonicalHash) + l.need.Add(FlagCanonicalHash) // Log data after the block we are sealing remains to be seen if l.logsSince == 0 { l.logHash = common.Hash{} l.execMsg = nil } - case entrydb.TypeCanonicalHash: - if !l.need.Any(entrydb.FlagCanonicalHash) { + case TypeCanonicalHash: + if !l.need.Any(FlagCanonicalHash) { return errors.New("not ready for canonical hash entry, already sealed the last block") } canonHash, err := newCanonicalHashFromEntry(entry) @@ -181,8 +171,8 @@ func (l *logContext) processEntry(entry entrydb.Entry) error { return err } l.blockHash = canonHash.hash - l.need.Remove(entrydb.FlagCanonicalHash) - case entrydb.TypeInitiatingEvent: + l.need.Remove(FlagCanonicalHash) + case TypeInitiatingEvent: if !l.hasCompleteBlock() { return errors.New("did not complete block seal, cannot add log") } @@ -196,13 +186,13 @@ func (l *logContext) processEntry(entry entrydb.Entry) error { l.execMsg = nil // clear the old state l.logHash = evt.logHash if evt.hasExecMsg { - l.need.Add(entrydb.FlagExecutingLink | entrydb.FlagExecutingCheck) + l.need.Add(FlagExecutingLink | FlagExecutingCheck) } else { l.logsSince += 1 } - l.need.Remove(entrydb.FlagInitiatingEvent) - case entrydb.TypeExecutingLink: - if !l.need.Any(entrydb.FlagExecutingLink) { + l.need.Remove(FlagInitiatingEvent) + case TypeExecutingLink: + if !l.need.Any(FlagExecutingLink) { return errors.New("unexpected executing-link") } link, err := newExecutingLinkFromEntry(entry) @@ -210,19 +200,19 @@ func (l *logContext) processEntry(entry entrydb.Entry) error { return err } l.execMsg = &types.ExecutingMessage{ - Chain: link.chain, + Chain: types.ChainIndex(link.chain), // TODO(#11105): translate chain ID to chain index BlockNum: link.blockNum, LogIdx: link.logIdx, Timestamp: link.timestamp, Hash: common.Hash{}, // not known yet } - l.need.Remove(entrydb.FlagExecutingLink) - l.need.Add(entrydb.FlagExecutingCheck) - case entrydb.TypeExecutingCheck: - if l.need.Any(entrydb.FlagExecutingLink) { + l.need.Remove(FlagExecutingLink) + l.need.Add(FlagExecutingCheck) + case TypeExecutingCheck: + if l.need.Any(FlagExecutingLink) { return errors.New("need executing link to be applied before the check part") } - if !l.need.Any(entrydb.FlagExecutingCheck) { + if !l.need.Any(FlagExecutingCheck) { return errors.New("unexpected executing check") } link, err := newExecutingCheckFromEntry(entry) @@ -230,13 +220,13 @@ func (l *logContext) processEntry(entry entrydb.Entry) error { return err } l.execMsg.Hash = link.hash - l.need.Remove(entrydb.FlagExecutingCheck) + l.need.Remove(FlagExecutingCheck) l.logsSince += 1 - case entrydb.TypePadding: - if l.need.Any(entrydb.FlagPadding) { - l.need.Remove(entrydb.FlagPadding) + case TypePadding: + if l.need.Any(FlagPadding) { + l.need.Remove(FlagPadding) } else { - l.need.Remove(entrydb.FlagPadding2) + l.need.Remove(FlagPadding2) } default: return fmt.Errorf("unknown entry type: %s", entry.Type()) @@ -253,77 +243,75 @@ func (l *logContext) appendEntry(obj EntryObj) { l.nextEntryIndex += 1 } -// infer advances the logContext in cases where multiple entries are to be appended implicitly -// depending on the last type of entry, a new entry is appended, -// or when the searchCheckpoint should be inserted. -// This can be done repeatedly until there is no more implied data to extend. +// infer advances the logContext in cases where complex entries contain multiple implied entries +// eg. a SearchCheckpoint implies a CannonicalHash will follow +// this also handles inserting the searchCheckpoint at the set frequency, and padding entries func (l *logContext) infer() error { // We force-insert a checkpoint whenever we hit the known fixed interval. if l.nextEntryIndex%searchCheckpointFrequency == 0 { - l.need.Add(entrydb.FlagSearchCheckpoint) + l.need.Add(FlagSearchCheckpoint) } - if l.need.Any(entrydb.FlagSearchCheckpoint) { + if l.need.Any(FlagSearchCheckpoint) { l.appendEntry(newSearchCheckpoint(l.blockNum, l.logsSince, l.timestamp)) - l.need.Add(entrydb.FlagCanonicalHash) // always follow with a canonical hash - l.need.Remove(entrydb.FlagSearchCheckpoint) + l.need.Add(FlagCanonicalHash) // always follow with a canonical hash + l.need.Remove(FlagSearchCheckpoint) return nil } - if l.need.Any(entrydb.FlagCanonicalHash) { + if l.need.Any(FlagCanonicalHash) { l.appendEntry(newCanonicalHash(l.blockHash)) - l.need.Remove(entrydb.FlagCanonicalHash) + l.need.Remove(FlagCanonicalHash) return nil } - if l.need.Any(entrydb.FlagPadding) { + if l.need.Any(FlagPadding) { l.appendEntry(paddingEntry{}) - l.need.Remove(entrydb.FlagPadding) + l.need.Remove(FlagPadding) return nil } - if l.need.Any(entrydb.FlagPadding2) { + if l.need.Any(FlagPadding2) { l.appendEntry(paddingEntry{}) - l.need.Remove(entrydb.FlagPadding2) + l.need.Remove(FlagPadding2) return nil } - if l.need.Any(entrydb.FlagInitiatingEvent) { + if l.need.Any(FlagInitiatingEvent) { // If we are running out of space for log-event data, // write some checkpoints as padding, to pass the checkpoint. if l.execMsg != nil { // takes 3 total. Need to avoid the checkpoint. switch l.nextEntryIndex % searchCheckpointFrequency { case searchCheckpointFrequency - 1: - l.need.Add(entrydb.FlagPadding) + l.need.Add(FlagPadding) return nil case searchCheckpointFrequency - 2: - l.need.Add(entrydb.FlagPadding | entrydb.FlagPadding2) + l.need.Add(FlagPadding | FlagPadding2) return nil } } evt := newInitiatingEvent(l.logHash, l.execMsg != nil) l.appendEntry(evt) - l.need.Remove(entrydb.FlagInitiatingEvent) + l.need.Remove(FlagInitiatingEvent) if l.execMsg == nil { l.logsSince += 1 } return nil } - if l.need.Any(entrydb.FlagExecutingLink) { + if l.need.Any(FlagExecutingLink) { link, err := newExecutingLink(*l.execMsg) if err != nil { return fmt.Errorf("failed to create executing link: %w", err) } l.appendEntry(link) - l.need.Remove(entrydb.FlagExecutingLink) + l.need.Remove(FlagExecutingLink) return nil } - if l.need.Any(entrydb.FlagExecutingCheck) { + if l.need.Any(FlagExecutingCheck) { l.appendEntry(newExecutingCheck(l.execMsg.Hash)) - l.need.Remove(entrydb.FlagExecutingCheck) + l.need.Remove(FlagExecutingCheck) l.logsSince += 1 return nil } return io.EOF } -// inferFull advances the queued entries held by the log context repeatedly -// until no more implied entries can be added +// inferFull advances the logContext until it cannot infer any more entries. func (l *logContext) inferFull() error { for i := 0; i < 10; i++ { err := l.infer() @@ -364,13 +352,13 @@ func (l *logContext) SealBlock(parent common.Hash, upd eth.BlockID, timestamp ui return err } if l.blockHash != parent { - return fmt.Errorf("%w: cannot apply block %s (parent %s) on top of %s", ErrConflict, upd, parent, l.blockHash) + return fmt.Errorf("%w: cannot apply block %s (parent %s) on top of %s", types.ErrConflict, upd, parent, l.blockHash) } if l.blockHash != (common.Hash{}) && l.blockNum+1 != upd.Number { - return fmt.Errorf("%w: cannot apply block %d on top of %d", ErrConflict, upd.Number, l.blockNum) + return fmt.Errorf("%w: cannot apply block %d on top of %d", types.ErrConflict, upd.Number, l.blockNum) } if l.timestamp > timestamp { - return fmt.Errorf("%w: block timestamp %d must be equal or larger than current timestamp %d", ErrConflict, timestamp, l.timestamp) + return fmt.Errorf("%w: block timestamp %d must be equal or larger than current timestamp %d", types.ErrConflict, timestamp, l.timestamp) } } l.blockHash = upd.Hash @@ -379,7 +367,7 @@ func (l *logContext) SealBlock(parent common.Hash, upd eth.BlockID, timestamp ui l.logsSince = 0 l.execMsg = nil l.logHash = common.Hash{} - l.need.Add(entrydb.FlagSearchCheckpoint) + l.need.Add(FlagSearchCheckpoint) return l.inferFull() // apply to the state as much as possible } @@ -387,34 +375,34 @@ func (l *logContext) SealBlock(parent common.Hash, upd eth.BlockID, timestamp ui // The parent-block that the log comes after must be applied with ApplyBlock first. func (l *logContext) ApplyLog(parentBlock eth.BlockID, logIdx uint32, logHash common.Hash, execMsg *types.ExecutingMessage) error { if parentBlock == (eth.BlockID{}) { - return fmt.Errorf("genesis does not have logs: %w", ErrLogOutOfOrder) + return fmt.Errorf("genesis does not have logs: %w", types.ErrOutOfOrder) } if err := l.inferFull(); err != nil { // ensure we can start applying return err } if !l.hasCompleteBlock() { if l.blockNum == 0 { - return fmt.Errorf("%w: should not have logs in block 0", ErrLogOutOfOrder) + return fmt.Errorf("%w: should not have logs in block 0", types.ErrOutOfOrder) } else { return errors.New("cannot append log before last known block is sealed") } } // check parent block if l.blockHash != parentBlock.Hash { - return fmt.Errorf("%w: log builds on top of block %s, but have block %s", ErrLogOutOfOrder, parentBlock, l.blockHash) + return fmt.Errorf("%w: log builds on top of block %s, but have block %s", types.ErrOutOfOrder, parentBlock, l.blockHash) } if l.blockNum != parentBlock.Number { - return fmt.Errorf("%w: log builds on top of block %d, but have block %d", ErrLogOutOfOrder, parentBlock.Number, l.blockNum) + return fmt.Errorf("%w: log builds on top of block %d, but have block %d", types.ErrOutOfOrder, parentBlock.Number, l.blockNum) } // check if log fits on top. The length so far == the index of the next log. if logIdx != l.logsSince { - return fmt.Errorf("%w: expected event index %d, cannot append %d", ErrLogOutOfOrder, l.logsSince, logIdx) + return fmt.Errorf("%w: expected event index %d, cannot append %d", types.ErrOutOfOrder, l.logsSince, logIdx) } l.logHash = logHash l.execMsg = execMsg - l.need.Add(entrydb.FlagInitiatingEvent) + l.need.Add(FlagInitiatingEvent) if execMsg != nil { - l.need.Add(entrydb.FlagExecutingLink | entrydb.FlagExecutingCheck) + l.need.Add(FlagExecutingLink | FlagExecutingCheck) } return l.inferFull() // apply to the state as much as possible } diff --git a/op-supervisor/supervisor/backend/db/open.go b/op-supervisor/supervisor/backend/db/open.go new file mode 100644 index 0000000000000..972a466fb35e0 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/open.go @@ -0,0 +1,47 @@ +package db + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/fromda" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func OpenLogDB(logger log.Logger, chainID types.ChainID, dataDir string, m logs.Metrics) (*logs.DB, error) { + path, err := prepLogDBPath(chainID, dataDir) + if err != nil { + return nil, fmt.Errorf("failed to create datadir for chain %s: %w", chainID, err) + } + logDB, err := logs.NewFromFile(logger, m, path, true) + if err != nil { + return nil, fmt.Errorf("failed to create logdb for chain %s at %v: %w", chainID, path, err) + } + return logDB, nil +} + +func OpenLocalDerivedFromDB(logger log.Logger, chainID types.ChainID, dataDir string, m fromda.ChainMetrics) (*fromda.DB, error) { + path, err := prepLocalDerivedFromDBPath(chainID, dataDir) + if err != nil { + return nil, fmt.Errorf("failed to prepare datadir for chain %s: %w", chainID, err) + } + db, err := fromda.NewFromFile(logger, fromda.AdaptMetrics(m, "local_derived"), path) + if err != nil { + return nil, fmt.Errorf("failed to create local-derived for chain %s at %q: %w", chainID, path, err) + } + return db, nil +} + +func OpenCrossDerivedFromDB(logger log.Logger, chainID types.ChainID, dataDir string, m fromda.ChainMetrics) (*fromda.DB, error) { + path, err := prepCrossDerivedFromDBPath(chainID, dataDir) + if err != nil { + return nil, fmt.Errorf("failed to prepare datadir for chain %s: %w", chainID, err) + } + db, err := fromda.NewFromFile(logger, fromda.AdaptMetrics(m, "cross_derived"), path) + if err != nil { + return nil, fmt.Errorf("failed to create cross-derived for chain %s at %q: %w", chainID, path, err) + } + return db, nil +} diff --git a/op-supervisor/supervisor/backend/db/query.go b/op-supervisor/supervisor/backend/db/query.go new file mode 100644 index 0000000000000..bbee01d71b4d0 --- /dev/null +++ b/op-supervisor/supervisor/backend/db/query.go @@ -0,0 +1,374 @@ +package db + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func (db *ChainsDB) FindSealedBlock(chain types.ChainID, number uint64) (seal types.BlockSeal, err error) { + logDB, ok := db.logDBs.Get(chain) + if !ok { + return types.BlockSeal{}, fmt.Errorf("%w: %v", types.ErrUnknownChain, chain) + } + return logDB.FindSealedBlock(number) +} + +// LatestBlockNum returns the latest fully-sealed block number that has been recorded to the logs db +// for the given chain. It does not contain safety guarantees. +// The block number might not be available (empty database, or non-existent chain). +func (db *ChainsDB) LatestBlockNum(chain types.ChainID) (num uint64, ok bool) { + logDB, knownChain := db.logDBs.Get(chain) + if !knownChain { + return 0, false + } + return logDB.LatestSealedBlockNum() +} + +func (db *ChainsDB) IsCrossUnsafe(chainID types.ChainID, block eth.BlockID) error { + v, ok := db.crossUnsafe.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + crossUnsafe := v.Get() + if crossUnsafe == (types.BlockSeal{}) { + return types.ErrFuture + } + if block.Number > crossUnsafe.Number { + return types.ErrFuture + } + // TODO(#11693): make cross-unsafe reorg safe + return nil +} + +func (db *ChainsDB) ParentBlock(chainID types.ChainID, parentOf eth.BlockID) (parent eth.BlockID, err error) { + logDB, ok := db.logDBs.Get(chainID) + if !ok { + return eth.BlockID{}, types.ErrUnknownChain + } + if parentOf.Number == 0 { + return eth.BlockID{}, nil + } + // TODO(#11693): make parent-lookup reorg safe + got, err := logDB.FindSealedBlock(parentOf.Number - 1) + if err != nil { + return eth.BlockID{}, err + } + return got.ID(), nil +} + +func (db *ChainsDB) IsLocalUnsafe(chainID types.ChainID, block eth.BlockID) error { + logDB, ok := db.logDBs.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + got, err := logDB.FindSealedBlock(block.Number) + if err != nil { + return err + } + if got.ID() != block { + return fmt.Errorf("found %s but was looking for unsafe block %s: %w", got, block, types.ErrConflict) + } + return nil +} + +func (db *ChainsDB) LocalUnsafe(chainID types.ChainID) (types.BlockSeal, error) { + eventsDB, ok := db.logDBs.Get(chainID) + if !ok { + return types.BlockSeal{}, types.ErrUnknownChain + } + n, ok := eventsDB.LatestSealedBlockNum() + if !ok { + return types.BlockSeal{}, types.ErrFuture + } + return eventsDB.FindSealedBlock(n) +} + +func (db *ChainsDB) CrossUnsafe(chainID types.ChainID) (types.BlockSeal, error) { + result, ok := db.crossUnsafe.Get(chainID) + if !ok { + return types.BlockSeal{}, types.ErrUnknownChain + } + crossUnsafe := result.Get() + // Fall back to cross-safe if cross-unsafe is not known yet + if crossUnsafe == (types.BlockSeal{}) { + _, crossSafe, err := db.CrossSafe(chainID) + if err != nil { + return types.BlockSeal{}, fmt.Errorf("no cross-unsafe known for chain %s, and failed to fall back to cross-safe value: %w", chainID, err) + } + return crossSafe, nil + } + return crossUnsafe, nil +} + +func (db *ChainsDB) LocalSafe(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { + localDB, ok := db.localDBs.Get(chainID) + if !ok { + return types.BlockSeal{}, types.BlockSeal{}, types.ErrUnknownChain + } + return localDB.Latest() +} + +func (db *ChainsDB) CrossSafe(chainID types.ChainID) (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { + crossDB, ok := db.crossDBs.Get(chainID) + if !ok { + return types.BlockSeal{}, types.BlockSeal{}, types.ErrUnknownChain + } + return crossDB.Latest() +} + +func (db *ChainsDB) Finalized(chainID types.ChainID) (types.BlockSeal, error) { + finalizedL1 := db.finalizedL1.Get() + if finalizedL1 == (eth.L1BlockRef{}) { + return types.BlockSeal{}, errors.New("no finalized L1 signal, cannot determine L2 finality yet") + } + derived, err := db.LastDerivedFrom(chainID, finalizedL1.ID()) + if err != nil { + return types.BlockSeal{}, errors.New("could not find what was last derived from the finalized L1 block") + } + return derived, nil +} + +func (db *ChainsDB) LastDerivedFrom(chainID types.ChainID, derivedFrom eth.BlockID) (derived types.BlockSeal, err error) { + crossDB, ok := db.crossDBs.Get(chainID) + if !ok { + return types.BlockSeal{}, types.ErrUnknownChain + } + return crossDB.LastDerivedAt(derivedFrom) +} + +// CrossDerivedFromBlockRef returns the block that the given block was derived from, if it exists in the cross derived-from storage. +// This includes the parent-block lookup. Use CrossDerivedFrom if no parent-block info is needed. +func (db *ChainsDB) CrossDerivedFromBlockRef(chainID types.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) { + xdb, ok := db.crossDBs.Get(chainID) + if !ok { + return eth.BlockRef{}, types.ErrUnknownChain + } + res, err := xdb.DerivedFrom(derived) + if err != nil { + return eth.BlockRef{}, err + } + parent, err := xdb.PreviousDerivedFrom(res.ID()) + // if we are working with the first item in the database, PreviousDerivedFrom will return ErrPreviousToFirst + // in which case we can attach a zero parent to the cross-derived-from block, as the parent block is unknown + if errors.Is(err, types.ErrPreviousToFirst) { + return res.ForceWithParent(eth.BlockID{}), nil + } else if err != nil { + return eth.BlockRef{}, err + } + return res.MustWithParent(parent.ID()), nil +} + +// Check calls the underlying logDB to determine if the given log entry exists at the given location. +// If the block-seal of the block that includes the log is known, it is returned. It is fully zeroed otherwise, if the block is in-progress. +func (db *ChainsDB) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (includedIn types.BlockSeal, err error) { + logDB, ok := db.logDBs.Get(chain) + if !ok { + return types.BlockSeal{}, fmt.Errorf("%w: %v", types.ErrUnknownChain, chain) + } + return logDB.Contains(blockNum, logIdx, logHash) +} + +// OpenBlock returns the Executing Messages for the block at the given number on the given chain. +// it routes the request to the appropriate logDB. +func (db *ChainsDB) OpenBlock(chainID types.ChainID, blockNum uint64) (seal eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + logDB, ok := db.logDBs.Get(chainID) + if !ok { + return eth.BlockRef{}, 0, nil, types.ErrUnknownChain + } + return logDB.OpenBlock(blockNum) +} + +// LocalDerivedFrom returns the block that the given block was derived from, if it exists in the local derived-from storage. +// it routes the request to the appropriate localDB. +func (db *ChainsDB) LocalDerivedFrom(chain types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) { + lDB, ok := db.localDBs.Get(chain) + if !ok { + return types.BlockSeal{}, types.ErrUnknownChain + } + return lDB.DerivedFrom(derived) +} + +// CrossDerivedFrom returns the block that the given block was derived from, if it exists in the cross derived-from storage. +// it routes the request to the appropriate crossDB. +func (db *ChainsDB) CrossDerivedFrom(chain types.ChainID, derived eth.BlockID) (derivedFrom types.BlockSeal, err error) { + xDB, ok := db.crossDBs.Get(chain) + if !ok { + return types.BlockSeal{}, types.ErrUnknownChain + } + return xDB.DerivedFrom(derived) +} + +// CandidateCrossSafe returns the candidate local-safe block that may become cross-safe. +// +// This returns ErrFuture if no block is known yet. +// +// Or ErrConflict if there is an inconsistency between the local-safe and cross-safe DB. +// +// Or ErrOutOfScope, with non-zero derivedFromScope, +// if additional L1 data is needed to cross-verify the candidate L2 block. +func (db *ChainsDB) CandidateCrossSafe(chain types.ChainID) (derivedFromScope, crossSafe eth.BlockRef, err error) { + xDB, ok := db.crossDBs.Get(chain) + if !ok { + return eth.BlockRef{}, eth.BlockRef{}, types.ErrUnknownChain + } + + lDB, ok := db.localDBs.Get(chain) + if !ok { + return eth.BlockRef{}, eth.BlockRef{}, types.ErrUnknownChain + } + + // Example: + // A B C D <- L1 + // 1 2 <- L2 + // return: + // (A, 0) -> initial scope, no L2 block yet. Genesis found to be cross-safe + // (A, 1) -> 1 is determined cross-safe, won't be a candidate anymore after. 2 is the new candidate + // (B, 2) -> 2 is out of scope, go to B + // (C, 2) -> 2 is out of scope, go to C + // (D, 2) -> 2 is in scope, stay on D, promote candidate to cross-safe + // (D, 3) -> look at 3 next, see if we have to bump L1 yet, try with same L1 scope first + + crossDerivedFrom, crossDerived, err := xDB.Latest() + if err != nil { + if errors.Is(err, types.ErrFuture) { + // If we do not have any cross-safe block yet, then return the first local-safe block. + derivedFrom, derived, err := lDB.First() + if err != nil { + return eth.BlockRef{}, eth.BlockRef{}, fmt.Errorf("failed to find first local-safe block: %w", err) + } + // the first derivedFrom (L1 block) is unlikely to be the genesis block, + derivedFromRef, err := derivedFrom.WithParent(eth.BlockID{}) + if err != nil { + // if the first derivedFrom isn't the genesis block, just warn and continue anyway + db.logger.Warn("First DerivedFrom is not genesis block") + derivedFromRef = derivedFrom.ForceWithParent(eth.BlockID{}) + } + // the first derived must be the genesis block, panic otherwise + derivedRef := derived.MustWithParent(eth.BlockID{}) + return derivedFromRef, derivedRef, nil + } + return eth.BlockRef{}, eth.BlockRef{}, err + } + // Find the local-safe block that comes right after the last seen cross-safe block. + // Just L2 block by block traversal, conditional on being local-safe. + // This will be the candidate L2 block to promote. + + // While the local-safe block isn't cross-safe given limited L1 scope, we'll keep bumping the L1 scope, + // And update cross-safe accordingly. + // This method will keep returning the latest known scope that has been verified to be cross-safe. + candidateFrom, candidate, err := lDB.NextDerived(crossDerived.ID()) + if err != nil { + return eth.BlockRef{}, eth.BlockRef{}, err + } + + candidateRef := candidate.MustWithParent(crossDerived.ID()) + + parentDerivedFrom, err := lDB.PreviousDerivedFrom(candidateFrom.ID()) + // if we are working with the first item in the database, PreviousDerivedFrom will return ErrPreviousToFirst + // in which case we can attach a zero parent to the cross-derived-from block, as the parent block is unknown + if errors.Is(err, types.ErrPreviousToFirst) { + parentDerivedFrom = types.BlockSeal{} + } else if err != nil { + return eth.BlockRef{}, eth.BlockRef{}, fmt.Errorf("failed to find parent-block of derived-from %s: %w", candidateFrom, err) + } + candidateFromRef := candidateFrom.MustWithParent(parentDerivedFrom.ID()) + + // Allow increment of DA by 1, if we know the floor (due to local safety) is 1 ahead of the current cross-safe L1 scope. + if candidateFrom.Number > crossDerivedFrom.Number+1 { + // If we are not ready to process the candidate block, + // then we need to stick to the current scope, so the caller can bump up from there. + var crossDerivedFromRef eth.BlockRef + parent, err := lDB.PreviousDerivedFrom(crossDerivedFrom.ID()) + // if we are working with the first item in the database, PreviousDerivedFrom will return ErrPreviousToFirst + // in which case we can attach a zero parent to the cross-derived-from block, as the parent block is unknown + if errors.Is(err, types.ErrPreviousToFirst) { + crossDerivedFromRef = crossDerivedFrom.ForceWithParent(eth.BlockID{}) + } else if err != nil { + return eth.BlockRef{}, eth.BlockRef{}, + fmt.Errorf("failed to find parent-block of cross-derived-from %s: %w", crossDerivedFrom, err) + } else { + crossDerivedFromRef = crossDerivedFrom.MustWithParent(parent.ID()) + } + return crossDerivedFromRef, eth.BlockRef{}, + fmt.Errorf("candidate is from %s, while current scope is %s: %w", + candidateFrom, crossDerivedFrom, types.ErrOutOfScope) + } + return candidateFromRef, candidateRef, nil +} + +func (db *ChainsDB) PreviousDerived(chain types.ChainID, derived eth.BlockID) (prevDerived types.BlockSeal, err error) { + lDB, ok := db.localDBs.Get(chain) + if !ok { + return types.BlockSeal{}, types.ErrUnknownChain + } + return lDB.PreviousDerived(derived) +} + +func (db *ChainsDB) PreviousDerivedFrom(chain types.ChainID, derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error) { + lDB, ok := db.localDBs.Get(chain) + if !ok { + return types.BlockSeal{}, types.ErrUnknownChain + } + return lDB.PreviousDerivedFrom(derivedFrom) +} + +func (db *ChainsDB) NextDerivedFrom(chain types.ChainID, derivedFrom eth.BlockID) (after eth.BlockRef, err error) { + lDB, ok := db.localDBs.Get(chain) + if !ok { + return eth.BlockRef{}, types.ErrUnknownChain + } + v, err := lDB.NextDerivedFrom(derivedFrom) + if err != nil { + return eth.BlockRef{}, err + } + return v.MustWithParent(derivedFrom), nil +} + +// Safest returns the strongest safety level that can be guaranteed for the given log entry. +// it assumes the log entry has already been checked and is valid, this function only checks safety levels. +// Safety levels are assumed to graduate from LocalUnsafe to LocalSafe to CrossUnsafe to CrossSafe, with Finalized as the strongest. +func (db *ChainsDB) Safest(chainID types.ChainID, blockNum uint64, index uint32) (safest types.SafetyLevel, err error) { + if finalized, err := db.Finalized(chainID); err == nil { + if finalized.Number >= blockNum { + return types.Finalized, nil + } + } + _, crossSafe, err := db.CrossSafe(chainID) + if err != nil { + return types.Invalid, err + } + if crossSafe.Number >= blockNum { + return types.CrossSafe, nil + } + crossUnsafe, err := db.CrossUnsafe(chainID) + if err != nil { + return types.Invalid, err + } + // TODO(#12425): API: "index" for in-progress block building shouldn't be exposed from DB. + // For now we're not counting anything cross-safe until the block is sealed. + if blockNum <= crossUnsafe.Number { + return types.CrossUnsafe, nil + } + _, localSafe, err := db.LocalSafe(chainID) + if err != nil { + return types.Invalid, err + } + if blockNum <= localSafe.Number { + return types.LocalSafe, nil + } + return types.LocalUnsafe, nil +} + +func (db *ChainsDB) IteratorStartingAt(chain types.ChainID, sealedNum uint64, logIndex uint32) (logs.Iterator, error) { + logDB, ok := db.logDBs.Get(chain) + if !ok { + return nil, fmt.Errorf("%w: %v", types.ErrUnknownChain, chain) + } + return logDB.IteratorStartingAt(sealedNum, logIndex) +} diff --git a/op-supervisor/supervisor/backend/db/update.go b/op-supervisor/supervisor/backend/db/update.go new file mode 100644 index 0000000000000..7ae7fde58a8bd --- /dev/null +++ b/op-supervisor/supervisor/backend/db/update.go @@ -0,0 +1,88 @@ +package db + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func (db *ChainsDB) AddLog( + chain types.ChainID, + logHash common.Hash, + parentBlock eth.BlockID, + logIdx uint32, + execMsg *types.ExecutingMessage) error { + logDB, ok := db.logDBs.Get(chain) + if !ok { + return fmt.Errorf("cannot AddLog: %w: %v", types.ErrUnknownChain, chain) + } + return logDB.AddLog(logHash, parentBlock, logIdx, execMsg) +} + +func (db *ChainsDB) SealBlock(chain types.ChainID, block eth.BlockRef) error { + logDB, ok := db.logDBs.Get(chain) + if !ok { + return fmt.Errorf("cannot SealBlock: %w: %v", types.ErrUnknownChain, chain) + } + err := logDB.SealBlock(block.ParentHash, block.ID(), block.Time) + if err != nil { + return fmt.Errorf("failed to seal block %v: %w", block, err) + } + db.logger.Info("Updated local unsafe", "chain", chain, "block", block) + return nil +} + +func (db *ChainsDB) Rewind(chain types.ChainID, headBlockNum uint64) error { + logDB, ok := db.logDBs.Get(chain) + if !ok { + return fmt.Errorf("cannot Rewind: %w: %s", types.ErrUnknownChain, chain) + } + return logDB.Rewind(headBlockNum) +} + +func (db *ChainsDB) UpdateLocalSafe(chain types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) error { + localDB, ok := db.localDBs.Get(chain) + if !ok { + return fmt.Errorf("cannot UpdateLocalSafe: %w: %v", types.ErrUnknownChain, chain) + } + db.logger.Debug("Updating local safe", "chain", chain, "derivedFrom", derivedFrom, "lastDerived", lastDerived) + return localDB.AddDerived(derivedFrom, lastDerived) +} + +func (db *ChainsDB) UpdateCrossUnsafe(chain types.ChainID, crossUnsafe types.BlockSeal) error { + v, ok := db.crossUnsafe.Get(chain) + if !ok { + return fmt.Errorf("cannot UpdateCrossUnsafe: %w: %s", types.ErrUnknownChain, chain) + } + v.Set(crossUnsafe) + db.logger.Info("Updated cross-unsafe", "chain", chain, "crossUnsafe", crossUnsafe) + return nil +} + +func (db *ChainsDB) UpdateCrossSafe(chain types.ChainID, l1View eth.BlockRef, lastCrossDerived eth.BlockRef) error { + crossDB, ok := db.crossDBs.Get(chain) + if !ok { + return fmt.Errorf("cannot UpdateCrossSafe: %w: %s", types.ErrUnknownChain, chain) + } + if err := crossDB.AddDerived(l1View, lastCrossDerived); err != nil { + return err + } + db.logger.Info("Updated cross-safe", "chain", chain, "l1View", l1View, "lastCrossDerived", lastCrossDerived) + return nil +} + +func (db *ChainsDB) UpdateFinalizedL1(finalized eth.BlockRef) error { + // Lock, so we avoid race-conditions in-between getting (for comparison) and setting. + db.finalizedL1.Lock() + defer db.finalizedL1.Unlock() + + if v := db.finalizedL1.Value; v.Number > finalized.Number { + return fmt.Errorf("cannot rewind finalized L1 head from %s to %s", v, finalized) + } + db.finalizedL1.Value = finalized + db.logger.Info("Updated finalized L1", "finalizedL1", finalized) + return nil +} diff --git a/op-supervisor/supervisor/backend/depset/depset.go b/op-supervisor/supervisor/backend/depset/depset.go new file mode 100644 index 0000000000000..da32b77190087 --- /dev/null +++ b/op-supervisor/supervisor/backend/depset/depset.go @@ -0,0 +1,41 @@ +package depset + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type DependencySetSource interface { + LoadDependencySet(ctx context.Context) (DependencySet, error) +} + +// DependencySet is an initialized dependency set, ready to answer queries +// of what is and what is not part of the dependency set. +type DependencySet interface { + + // CanExecuteAt determines if an executing message is valid at all. + // I.e. if the chain may be executing messages at the given timestamp. + // This may return an error if the query temporarily cannot be answered. + // E.g. if the DependencySet is syncing new changes. + CanExecuteAt(chainID types.ChainID, execTimestamp uint64) (bool, error) + + // CanInitiateAt determines if an initiating message is valid to pull in. + // I.e. if the message of the given chain is readable or not. + // This may return an error if the query temporarily cannot be answered. + // E.g. if the DependencySet is syncing new changes. + CanInitiateAt(chainID types.ChainID, initTimestamp uint64) (bool, error) + + // Chains returns the list of chains that are part of the dependency set. + Chains() []types.ChainID + + // HasChain determines if a chain is being tracked for interop purposes. + // See CanExecuteAt and CanInitiateAt to check if a chain may message at a given time. + HasChain(chainID types.ChainID) bool + + // ChainIndexFromID converts a ChainID to a ChainIndex. + ChainIndexFromID(id types.ChainID) (types.ChainIndex, error) + + // ChainIDFromIndex converts a ChainIndex to a ChainID. + ChainIDFromIndex(index types.ChainIndex) (types.ChainID, error) +} diff --git a/op-supervisor/supervisor/backend/depset/depset_test.go b/op-supervisor/supervisor/backend/depset/depset_test.go new file mode 100644 index 0000000000000..8b71b38ef46ed --- /dev/null +++ b/op-supervisor/supervisor/backend/depset/depset_test.go @@ -0,0 +1,79 @@ +package depset + +import ( + "context" + "encoding/json" + "os" + "path" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func TestDependencySet(t *testing.T) { + d := path.Join(t.TempDir(), "tmp_dep_set.json") + + depSet, err := NewStaticConfigDependencySet( + map[types.ChainID]*StaticConfigDependency{ + types.ChainIDFromUInt64(900): { + ChainIndex: 900, + ActivationTime: 42, + HistoryMinTime: 100, + }, + types.ChainIDFromUInt64(901): { + ChainIndex: 901, + ActivationTime: 30, + HistoryMinTime: 20, + }, + }) + require.NoError(t, err) + data, err := json.Marshal(depSet) + require.NoError(t, err) + + require.NoError(t, os.WriteFile(d, data, 0644)) + + loader := &JsonDependencySetLoader{Path: d} + result, err := loader.LoadDependencySet(context.Background()) + require.NoError(t, err) + + chainIDs := result.Chains() + require.Equal(t, []types.ChainID{ + types.ChainIDFromUInt64(900), + types.ChainIDFromUInt64(901), + }, chainIDs) + + v, err := result.CanExecuteAt(types.ChainIDFromUInt64(900), 42) + require.NoError(t, err) + require.True(t, v) + v, err = result.CanExecuteAt(types.ChainIDFromUInt64(900), 41) + require.NoError(t, err) + require.False(t, v) + v, err = result.CanInitiateAt(types.ChainIDFromUInt64(900), 100) + require.NoError(t, err) + require.True(t, v) + v, err = result.CanInitiateAt(types.ChainIDFromUInt64(900), 99) + require.NoError(t, err) + require.False(t, v) + + v, err = result.CanExecuteAt(types.ChainIDFromUInt64(901), 30) + require.NoError(t, err) + require.True(t, v) + v, err = result.CanExecuteAt(types.ChainIDFromUInt64(901), 29) + require.NoError(t, err) + require.False(t, v) + v, err = result.CanInitiateAt(types.ChainIDFromUInt64(901), 20) + require.NoError(t, err) + require.True(t, v) + v, err = result.CanInitiateAt(types.ChainIDFromUInt64(901), 19) + require.NoError(t, err) + require.False(t, v) + + v, err = result.CanExecuteAt(types.ChainIDFromUInt64(902), 100000) + require.NoError(t, err) + require.False(t, v, "902 not a dependency") + v, err = result.CanInitiateAt(types.ChainIDFromUInt64(902), 100000) + require.NoError(t, err) + require.False(t, v, "902 not a dependency") +} diff --git a/op-supervisor/supervisor/backend/depset/json.go b/op-supervisor/supervisor/backend/depset/json.go new file mode 100644 index 0000000000000..7f381928a075f --- /dev/null +++ b/op-supervisor/supervisor/backend/depset/json.go @@ -0,0 +1,29 @@ +package depset + +import ( + "context" + "encoding/json" + "fmt" + "os" +) + +// JsonDependencySetLoader loads a dependency set from a file-path. +type JsonDependencySetLoader struct { + Path string +} + +func (j *JsonDependencySetLoader) LoadDependencySet(ctx context.Context) (DependencySet, error) { + f, err := os.Open(j.Path) + if err != nil { + return nil, fmt.Errorf("failed to open dependency set: %w", err) + } + defer f.Close() + dec := json.NewDecoder(f) + var out StaticConfigDependencySet + if err := dec.Decode(&out); err != nil { + return nil, fmt.Errorf("failed to decode dependency set: %w", err) + } + return &out, nil +} + +var _ DependencySetSource = (*JsonDependencySetLoader)(nil) diff --git a/op-supervisor/supervisor/backend/depset/static.go b/op-supervisor/supervisor/backend/depset/static.go new file mode 100644 index 0000000000000..f7cf1c166de53 --- /dev/null +++ b/op-supervisor/supervisor/backend/depset/static.go @@ -0,0 +1,133 @@ +package depset + +import ( + "context" + "encoding/json" + "fmt" + "slices" + "sort" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type StaticConfigDependency struct { + // ChainIndex is the unique short identifier of this chain. + ChainIndex types.ChainIndex `json:"chainIndex"` + + // ActivationTime is when the chain becomes part of the dependency set. + // This is the minimum timestamp of the inclusion of an executing message. + ActivationTime uint64 `json:"activationTime"` + + // HistoryMinTime is what the lower bound of data is to store. + // This is the minimum timestamp of an initiating message to be accessible to others. + // This is set to 0 when all data since genesis is executable. + HistoryMinTime uint64 `json:"historyMinTime"` +} + +// StaticConfigDependencySet statically declares a DependencySet. +// It can be used as a DependencySetSource itself, by simply returning the itself when loading the set. +type StaticConfigDependencySet struct { + // dependency info per chain + dependencies map[types.ChainID]*StaticConfigDependency + // cached mapping of chain index to chain ID + indexToID map[types.ChainIndex]types.ChainID + // cached list of chain IDs, sorted by ID value + chainIDs []types.ChainID +} + +func NewStaticConfigDependencySet(dependencies map[types.ChainID]*StaticConfigDependency) (*StaticConfigDependencySet, error) { + out := &StaticConfigDependencySet{dependencies: dependencies} + if err := out.hydrate(); err != nil { + return nil, err + } + return out, nil +} + +// jsonStaticConfigDependencySet is a util for JSON encoding/decoding, +// to encode/decode just the attributes that matter, +// while wrapping the decoding functionality with additional hydration step. +type jsonStaticConfigDependencySet struct { + Dependencies map[types.ChainID]*StaticConfigDependency `json:"dependencies"` +} + +func (ds *StaticConfigDependencySet) MarshalJSON() ([]byte, error) { + out := &jsonStaticConfigDependencySet{ + Dependencies: ds.dependencies, + } + return json.Marshal(out) +} + +func (ds *StaticConfigDependencySet) UnmarshalJSON(data []byte) error { + var v jsonStaticConfigDependencySet + if err := json.Unmarshal(data, &v); err != nil { + return err + } + ds.dependencies = v.Dependencies + return ds.hydrate() +} + +// hydrate sets all the cached values, based on the dependencies attribute +func (ds *StaticConfigDependencySet) hydrate() error { + ds.indexToID = make(map[types.ChainIndex]types.ChainID) + ds.chainIDs = make([]types.ChainID, 0, len(ds.dependencies)) + for id, dep := range ds.dependencies { + if existing, ok := ds.indexToID[dep.ChainIndex]; ok { + return fmt.Errorf("chain %s cannot have the same index (%d) as chain %s", id, dep.ChainIndex, existing) + } + ds.indexToID[dep.ChainIndex] = id + ds.chainIDs = append(ds.chainIDs, id) + } + sort.Slice(ds.chainIDs, func(i, j int) bool { + return ds.chainIDs[i].Cmp(ds.chainIDs[j]) < 0 + }) + return nil +} + +var _ DependencySetSource = (*StaticConfigDependencySet)(nil) + +var _ DependencySet = (*StaticConfigDependencySet)(nil) + +func (ds *StaticConfigDependencySet) LoadDependencySet(ctx context.Context) (DependencySet, error) { + return ds, nil +} + +func (ds *StaticConfigDependencySet) CanExecuteAt(chainID types.ChainID, execTimestamp uint64) (bool, error) { + dep, ok := ds.dependencies[chainID] + if !ok { + return false, nil + } + return execTimestamp >= dep.ActivationTime, nil +} + +func (ds *StaticConfigDependencySet) CanInitiateAt(chainID types.ChainID, initTimestamp uint64) (bool, error) { + dep, ok := ds.dependencies[chainID] + if !ok { + return false, nil + } + return initTimestamp >= dep.HistoryMinTime, nil +} + +func (ds *StaticConfigDependencySet) Chains() []types.ChainID { + return slices.Clone(ds.chainIDs) +} + +func (ds *StaticConfigDependencySet) HasChain(chainID types.ChainID) bool { + _, ok := ds.dependencies[chainID] + return ok +} + +func (ds *StaticConfigDependencySet) ChainIndexFromID(id types.ChainID) (types.ChainIndex, error) { + dep, ok := ds.dependencies[id] + if !ok { + return 0, types.ErrUnknownChain + } + return dep.ChainIndex, nil +} + +func (ds *StaticConfigDependencySet) ChainIDFromIndex(index types.ChainIndex) (types.ChainID, error) { + id, ok := ds.indexToID[index] + if !ok { + return types.ChainID{}, types.ErrUnknownChain + } + return id, nil +} diff --git a/op-supervisor/supervisor/backend/mock.go b/op-supervisor/supervisor/backend/mock.go index 99ec630ddc362..b40c5209d5ef2 100644 --- a/op-supervisor/supervisor/backend/mock.go +++ b/op-supervisor/supervisor/backend/mock.go @@ -6,12 +6,10 @@ import ( "io" "sync/atomic" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" ) type MockBackend struct { @@ -52,14 +50,34 @@ func (m *MockBackend) CheckMessages(messages []types.Message, minSafety types.Sa return nil } -func (m *MockBackend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) { - return types.CrossUnsafe, nil +func (m *MockBackend) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) { + return types.ReferenceView{}, nil } -func (m *MockBackend) DerivedFrom(ctx context.Context, t types.ChainID, parentHash common.Hash, n uint64) (eth.BlockRef, error) { +func (m *MockBackend) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) { + return types.ReferenceView{}, nil +} + +func (m *MockBackend) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} + +func (m *MockBackend) CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) { return eth.BlockRef{}, nil } +func (m *MockBackend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error { + return nil +} + +func (m *MockBackend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) error { + return nil +} + +func (m *MockBackend) UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.BlockRef) error { + return nil +} + func (m *MockBackend) Close() error { return nil } diff --git a/op-supervisor/supervisor/backend/processors/chain_processor.go b/op-supervisor/supervisor/backend/processors/chain_processor.go new file mode 100644 index 0000000000000..1b4ffcc822e68 --- /dev/null +++ b/op-supervisor/supervisor/backend/processors/chain_processor.go @@ -0,0 +1,303 @@ +package processors + +import ( + "context" + "errors" + "fmt" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type Source interface { + L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) + FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, gethtypes.Receipts, error) +} + +type LogProcessor interface { + ProcessLogs(ctx context.Context, block eth.BlockRef, receipts gethtypes.Receipts) error +} + +type DatabaseRewinder interface { + Rewind(chain types.ChainID, headBlockNum uint64) error + LatestBlockNum(chain types.ChainID) (num uint64, ok bool) +} + +type BlockProcessorFn func(ctx context.Context, block eth.BlockRef) error + +func (fn BlockProcessorFn) ProcessBlock(ctx context.Context, block eth.BlockRef) error { + return fn(ctx, block) +} + +// ChainProcessor is a HeadProcessor that fills in any skipped blocks between head update events. +// It ensures that, absent reorgs, every block in the chain is processed even if some head advancements are skipped. +type ChainProcessor struct { + log log.Logger + + client Source + clientLock sync.Mutex + + chain types.ChainID + + processor LogProcessor + rewinder DatabaseRewinder + + // the last known head. May be 0 if not known. + lastHead atomic.Uint64 + // channel with capacity of 1, full if there is work to do + newHead chan struct{} + + // to signal to the other services that new indexed data is available + onIndexed func() + + // lifetime management of the chain processor + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + + maxFetcherThreads int +} + +func NewChainProcessor(log log.Logger, chain types.ChainID, processor LogProcessor, rewinder DatabaseRewinder, onIndexed func()) *ChainProcessor { + ctx, cancel := context.WithCancel(context.Background()) + out := &ChainProcessor{ + log: log.New("chain", chain), + client: nil, + chain: chain, + processor: processor, + rewinder: rewinder, + newHead: make(chan struct{}, 1), + onIndexed: onIndexed, + ctx: ctx, + cancel: cancel, + maxFetcherThreads: 10, + } + return out +} + +func (s *ChainProcessor) SetSource(cl Source) { + s.clientLock.Lock() + defer s.clientLock.Unlock() + s.client = cl +} + +func (s *ChainProcessor) StartBackground() { + s.wg.Add(1) + go s.worker() +} + +func (s *ChainProcessor) ProcessToHead() { + s.work() +} + +func (s *ChainProcessor) nextNum() uint64 { + headNum, ok := s.rewinder.LatestBlockNum(s.chain) + if !ok { + return 0 // genesis. We could change this to start at a later block. + } + return headNum + 1 +} + +// worker is the main loop of the chain processor's worker +// it manages work by request or on a timer, and watches for shutdown +func (s *ChainProcessor) worker() { + defer s.wg.Done() + + delay := time.NewTicker(time.Second * 5) + for { + // await next time we process, or detect shutdown + select { + case <-s.ctx.Done(): + delay.Stop() + return + case <-s.newHead: + s.log.Debug("Responding to new head signal") + s.work() + case <-delay.C: + s.log.Debug("Checking for updates") + s.work() + } + } +} + +// work processes the next block in the chain repeatedly until it reaches the head +func (s *ChainProcessor) work() { + for { + if s.ctx.Err() != nil { // check if we are closing down + return + } + _, err := s.rangeUpdate() + target := s.nextNum() + if err != nil { + if errors.Is(err, ethereum.NotFound) { + s.log.Debug("Event-indexer cannot find next block yet", "target", target, "err", err) + } else if errors.Is(err, types.ErrNoRPCSource) { + s.log.Warn("No RPC source configured, cannot process new blocks") + } else { + s.log.Error("Failed to process new block", "err", err) + } + } else if x := s.lastHead.Load(); target+1 <= x { + s.log.Debug("Continuing with next block", "newTarget", target+1, "lastHead", x) + continue // instantly continue processing, no need to idle + } else { + s.log.Debug("Idling block-processing, reached latest block", "head", target) + } + return + } +} + +func (s *ChainProcessor) rangeUpdate() (int, error) { + s.clientLock.Lock() + defer s.clientLock.Unlock() + if s.client == nil { + return 0, types.ErrNoRPCSource + } + + // define the range of blocks to fetch + // [next, last] inclusive with a max of s.fetcherThreads blocks + next := s.nextNum() + last := s.lastHead.Load() + // next is already beyond the end, nothing to do + if next > last { + return 0, nil + } + nums := make([]uint64, 0) + for i := next; i <= last; i++ { + nums = append(nums, i) + // only collect as many blocks as we can fetch in parallel + if len(nums) >= s.maxFetcherThreads { + break + } + } + + s.log.Debug("Fetching blocks", "chain", s.chain.String(), "next", next, "last", last, "count", len(nums)) + + // make a structure to receive parallel results + type keyedResult struct { + num uint64 + blockRef *eth.BlockRef + receipts gethtypes.Receipts + err error + } + parallelResults := make(chan keyedResult, len(nums)) + + // each thread will fetch a block and its receipts and send the result to the channel + fetch := func(wg *sync.WaitGroup, num uint64) { + defer wg.Done() + // ensure we emit the result at the end + result := keyedResult{num, nil, nil, nil} + defer func() { parallelResults <- result }() + + // fetch the block ref + ctx, cancel := context.WithTimeout(s.ctx, time.Second*10) + nextL1, err := s.client.L1BlockRefByNumber(ctx, num) + cancel() + if err != nil { + result.err = err + return + } + next := eth.BlockRef{ + Hash: nextL1.Hash, + ParentHash: nextL1.ParentHash, + Number: nextL1.Number, + Time: nextL1.Time, + } + result.blockRef = &next + + // fetch receipts + ctx, cancel = context.WithTimeout(s.ctx, time.Second*10) + _, receipts, err := s.client.FetchReceipts(ctx, next.Hash) + cancel() + if err != nil { + result.err = err + return + } + result.receipts = receipts + } + + // kick off the fetches and wait for them to complete + var wg sync.WaitGroup + for _, num := range nums { + wg.Add(1) + go fetch(&wg, num) + } + wg.Wait() + + // collect and sort the results + results := make([]keyedResult, len(nums)) + for i := range nums { + result := <-parallelResults + results[i] = result + } + slices.SortFunc(results, func(a, b keyedResult) int { + if a.num < b.num { + return -1 + } + if a.num > b.num { + return 1 + } + return 0 + }) + + // process the results in order and return the first error encountered, + // and the number of blocks processed successfully by this call + for i := range results { + if results[i].err != nil { + return i, fmt.Errorf("failed to fetch block %d: %w", results[i].num, results[i].err) + } + // process the receipts + err := s.process(s.ctx, *results[i].blockRef, results[i].receipts) + if err != nil { + return i, fmt.Errorf("failed to process block %d: %w", results[i].num, err) + } + } + return len(results), nil +} + +func (s *ChainProcessor) process(ctx context.Context, next eth.BlockRef, receipts gethtypes.Receipts) error { + if err := s.processor.ProcessLogs(ctx, next, receipts); err != nil { + s.log.Error("Failed to process block", "block", next, "err", err) + + if next.Number == 0 { // cannot rewind genesis + return nil + } + + // Try to rewind the database to the previous block to remove any logs from this block that were written + if err := s.rewinder.Rewind(s.chain, next.Number-1); err != nil { + // If any logs were written, our next attempt to write will fail and we'll retry this rewind. + // If no logs were written successfully then the rewind wouldn't have done anything anyway. + s.log.Error("Failed to rewind after error processing block", "block", next, "err", err) + } + return err + } + s.log.Info("Indexed block events", "block", next, "txs", len(receipts)) + s.onIndexed() + return nil + +} + +func (s *ChainProcessor) OnNewHead(head eth.BlockRef) error { + // update the latest target + s.lastHead.Store(head.Number) + // signal that we have something to process + select { + case s.newHead <- struct{}{}: + default: + // already requested an update + } + return nil +} + +func (s *ChainProcessor) Close() { + s.cancel() + s.wg.Wait() +} diff --git a/op-supervisor/supervisor/backend/processors/client.go b/op-supervisor/supervisor/backend/processors/client.go new file mode 100644 index 0000000000000..253308a514a3b --- /dev/null +++ b/op-supervisor/supervisor/backend/processors/client.go @@ -0,0 +1,28 @@ +package processors + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-service/sources/caching" +) + +// NewEthClient creates an Eth RPC client for event-log fetching. +func NewEthClient(ctx context.Context, logger log.Logger, m caching.Metrics, rpc string, rpcClient client.RPC, + pollRate time.Duration, trustRPC bool, kind sources.RPCProviderKind) (*sources.L1Client, error) { + c, err := client.NewRPCWithClient(ctx, logger, rpc, rpcClient, pollRate) + if err != nil { + return nil, fmt.Errorf("failed to create new RPC client: %w", err) + } + + l1Client, err := sources.NewL1Client(c, logger, m, sources.L1ClientSimpleConfig(trustRPC, kind, 100)) + if err != nil { + return nil, fmt.Errorf("failed to connect client: %w", err) + } + return l1Client, nil +} diff --git a/op-supervisor/supervisor/backend/processors/executing_message.go b/op-supervisor/supervisor/backend/processors/executing_message.go new file mode 100644 index 0000000000000..eedd8ce4b1e34 --- /dev/null +++ b/op-supervisor/supervisor/backend/processors/executing_message.go @@ -0,0 +1,38 @@ +package processors + +import ( + "fmt" + + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/interoptypes" + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type EventDecoderFn func(*ethTypes.Log) (*types.ExecutingMessage, error) + +func DecodeExecutingMessageLog(l *ethTypes.Log) (*types.ExecutingMessage, error) { + if l.Address != params.InteropCrossL2InboxAddress { + return nil, nil + } + if len(l.Topics) != 2 { // topics: event-id and payload-hash + return nil, nil + } + if l.Topics[0] != interoptypes.ExecutingMessageEventTopic { + return nil, nil + } + var msg interoptypes.Message + if err := msg.DecodeEvent(l.Topics, l.Data); err != nil { + return nil, fmt.Errorf("invalid executing message: %w", err) + } + logHash := types.PayloadHashToLogHash(msg.PayloadHash, msg.Identifier.Origin) + return &types.ExecutingMessage{ + // TODO(#11105): translate chain index to chain ID + Chain: types.ChainIndex(msg.Identifier.ChainID.Uint64()), + BlockNum: msg.Identifier.BlockNumber, + LogIdx: msg.Identifier.LogIndex, + Timestamp: msg.Identifier.Timestamp, + Hash: logHash, + }, nil +} diff --git a/op-supervisor/supervisor/backend/processors/executing_message_test.go b/op-supervisor/supervisor/backend/processors/executing_message_test.go new file mode 100644 index 0000000000000..6badeb2016726 --- /dev/null +++ b/op-supervisor/supervisor/backend/processors/executing_message_test.go @@ -0,0 +1,58 @@ +package processors + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func TestDecodeExecutingMessageLog(t *testing.T) { + data := ` +{ + "address": "0x4200000000000000000000000000000000000022", + "topics": [ + "0x5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba7", + "0xc3f57e1f0dd62a4f77787d834029bfeaab8894022c47edbe13b044fb658c9190" + ], + "data": "0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3000000000000000000000000000000000000000000000000000000000000119d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006724d56300000000000000000000000000000000000000000000000000000000000dbc68", + "blockHash": "0x355b82e9db9105fe3e7b7ed1897878ecbba8be7f30f94aca9dc55b6296a624cf", + "blockNumber": "0x13a8", + "transactionHash": "0x6eb22bb67562ac6a8fdbf60d6227c0b1f3f9d1d15ead1b0de055358f4fb9fa69", + "transactionIndex": "0x2", + "logIndex": "0x0", + "removed": false +} +` + var logEvent ethTypes.Log + require.NoError(t, json.Unmarshal([]byte(data), &logEvent)) + + msg, err := DecodeExecutingMessageLog(&logEvent) + require.NoError(t, err) + require.NotNil(t, msg) + + // struct Identifier { + // address origin; + // uint256 blockNumber; + // uint256 logIndex; + // uint256 timestamp; + // uint256 chainId; + // } + // function executeMessage(Identifier calldata _id, + // address _target, bytes calldata _message) external payable; + + originAddr := common.HexToAddress("0x5fbdb2315678afecb367f032d93f642f64180aa3") + payloadHash := common.HexToHash("0xc3f57e1f0dd62a4f77787d834029bfeaab8894022c47edbe13b044fb658c9190") + logHash := types.PayloadHashToLogHash(payloadHash, originAddr) + + require.Equal(t, logHash, msg.Hash) + require.Equal(t, uint64(4509), msg.BlockNum) + require.Equal(t, uint32(0), msg.LogIdx) + require.Equal(t, uint64(1730467171), msg.Timestamp) + require.Equal(t, types.ChainIndex(900200), msg.Chain) +} diff --git a/op-supervisor/supervisor/backend/source/log_processor.go b/op-supervisor/supervisor/backend/processors/log_processor.go similarity index 51% rename from op-supervisor/supervisor/backend/source/log_processor.go rename to op-supervisor/supervisor/backend/processors/log_processor.go index d7f7e1fbeae0b..2ca79c4d2f2f0 100644 --- a/op-supervisor/supervisor/backend/source/log_processor.go +++ b/op-supervisor/supervisor/backend/processors/log_processor.go @@ -1,8 +1,7 @@ -package source +package processors import ( "context" - "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -10,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/source/contracts" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -24,21 +22,17 @@ type ChainsDBClientForLogProcessor interface { AddLog(chain types.ChainID, logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *types.ExecutingMessage) error } -type EventDecoder interface { - DecodeExecutingMessageLog(log *ethTypes.Log) (types.ExecutingMessage, error) -} - type logProcessor struct { chain types.ChainID logStore LogStorage - eventDecoder EventDecoder + eventDecoder EventDecoderFn } -func newLogProcessor(chain types.ChainID, logStore LogStorage) *logProcessor { +func NewLogProcessor(chain types.ChainID, logStore LogStorage) LogProcessor { return &logProcessor{ chain: chain, logStore: logStore, - eventDecoder: contracts.NewCrossL2Inbox(), + eventDecoder: DecodeExecutingMessageLog, } } @@ -49,19 +43,15 @@ func (p *logProcessor) ProcessLogs(_ context.Context, block eth.BlockRef, rcpts for _, l := range rcpt.Logs { // log hash represents the hash of *this* log as a potentially initiating message logHash := logToLogHash(l) - var execMsg *types.ExecutingMessage - msg, err := p.eventDecoder.DecodeExecutingMessageLog(l) - if err != nil && !errors.Is(err, contracts.ErrEventNotFound) { - return fmt.Errorf("failed to decode executing message log: %w", err) - } else if err == nil { - // if the log is an executing message, store the message - execMsg = &msg + // The log may be an executing message emitted by the CrossL2Inbox + execMsg, err := p.eventDecoder(l) + if err != nil { + return fmt.Errorf("invalid log %d from block %s: %w", l.Index, block.ID(), err) } // executing messages have multiple entries in the database // they should start with the initiating message and then include the execution - err = p.logStore.AddLog(p.chain, logHash, block.ParentID(), uint32(l.Index), execMsg) - if err != nil { - return fmt.Errorf("failed to add log %d from block %v: %w", l.Index, block.ID(), err) + if err := p.logStore.AddLog(p.chain, logHash, block.ParentID(), uint32(l.Index), execMsg); err != nil { + return fmt.Errorf("failed to add log %d from block %s: %w", l.Index, block.ID(), err) } } } @@ -77,30 +67,6 @@ func (p *logProcessor) ProcessLogs(_ context.Context, block eth.BlockRef, rcpts // The address is hashed into the payload hash to save space in the log storage, // and because they represent paired data. func logToLogHash(l *ethTypes.Log) common.Hash { - payloadHash := crypto.Keccak256(logToMessagePayload(l)) - return payloadHashToLogHash(common.Hash(payloadHash), l.Address) -} - -// logToMessagePayload is the data that is hashed to get the logHash -// it is the concatenation of the log's topics and data -// the implementation is based on the interop messaging spec -func logToMessagePayload(l *ethTypes.Log) []byte { - msg := make([]byte, 0) - for _, topic := range l.Topics { - msg = append(msg, topic.Bytes()...) - } - msg = append(msg, l.Data...) - return msg -} - -// payloadHashToLogHash converts the payload hash to the log hash -// it is the concatenation of the log's address and the hash of the log's payload, -// which is then hashed. This is the hash that is stored in the log storage. -// The logHash can then be used to traverse from the executing message -// to the log the referenced initiating message. -func payloadHashToLogHash(payloadHash common.Hash, addr common.Address) common.Hash { - msg := make([]byte, 0, 2*common.HashLength) - msg = append(msg, addr.Bytes()...) - msg = append(msg, payloadHash.Bytes()...) - return crypto.Keccak256Hash(msg) + payloadHash := crypto.Keccak256Hash(types.LogToMessagePayload(l)) + return types.PayloadHashToLogHash(payloadHash, l.Address) } diff --git a/op-supervisor/supervisor/backend/source/log_processor_test.go b/op-supervisor/supervisor/backend/processors/log_processor_test.go similarity index 91% rename from op-supervisor/supervisor/backend/source/log_processor_test.go rename to op-supervisor/supervisor/backend/processors/log_processor_test.go index 2e1322f55aed1..f919d4f801ed8 100644 --- a/op-supervisor/supervisor/backend/source/log_processor_test.go +++ b/op-supervisor/supervisor/backend/processors/log_processor_test.go @@ -1,4 +1,4 @@ -package source +package processors import ( "context" @@ -25,7 +25,7 @@ func TestLogProcessor(t *testing.T) { } t.Run("NoOutputWhenLogsAreEmpty", func(t *testing.T) { store := &stubLogStorage{} - processor := newLogProcessor(logProcessorChainID, store) + processor := NewLogProcessor(logProcessorChainID, store) err := processor.ProcessLogs(ctx, block1, ethTypes.Receipts{}) require.NoError(t, err) @@ -59,7 +59,7 @@ func TestLogProcessor(t *testing.T) { }, } store := &stubLogStorage{} - processor := newLogProcessor(logProcessorChainID, store) + processor := NewLogProcessor(logProcessorChainID, store) err := processor.ProcessLogs(ctx, block1, rcpts) require.NoError(t, err) @@ -107,19 +107,19 @@ func TestLogProcessor(t *testing.T) { }, }, } - execMsg := types.ExecutingMessage{ - Chain: 4, + execMsg := &types.ExecutingMessage{ + Chain: 4, // TODO(#11105): translate chain ID to chain index BlockNum: 6, LogIdx: 8, Timestamp: 10, Hash: common.Hash{0xaa}, } store := &stubLogStorage{} - processor := newLogProcessor(types.ChainID{4}, store) - processor.eventDecoder = EventDecoderFn(func(l *ethTypes.Log) (types.ExecutingMessage, error) { + processor := NewLogProcessor(types.ChainID{4}, store).(*logProcessor) + processor.eventDecoder = func(l *ethTypes.Log) (*types.ExecutingMessage, error) { require.Equal(t, rcpts[0].Logs[0], l) return execMsg, nil - }) + } err := processor.ProcessLogs(ctx, block1, rcpts) require.NoError(t, err) @@ -128,7 +128,7 @@ func TestLogProcessor(t *testing.T) { parent: block1.ParentID(), logIdx: 0, logHash: logToLogHash(rcpts[0].Logs[0]), - execMsg: &execMsg, + execMsg: execMsg, }, } require.Equal(t, expected, store.logs) @@ -242,9 +242,3 @@ type storedLog struct { logHash common.Hash execMsg *types.ExecutingMessage } - -type EventDecoderFn func(*ethTypes.Log) (types.ExecutingMessage, error) - -func (f EventDecoderFn) DecodeExecutingMessageLog(log *ethTypes.Log) (types.ExecutingMessage, error) { - return f(log) -} diff --git a/op-supervisor/supervisor/backend/safety/safety.go b/op-supervisor/supervisor/backend/safety/safety.go deleted file mode 100644 index 326c72755e35c..0000000000000 --- a/op-supervisor/supervisor/backend/safety/safety.go +++ /dev/null @@ -1,270 +0,0 @@ -package safety - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -type SafetyIndex interface { - // Updaters for the latest local safety status of each chain - UpdateLocalUnsafe(chainID types.ChainID, ref eth.BlockRef) error - UpdateLocalSafe(chainID types.ChainID, at eth.BlockRef, ref eth.BlockRef) error - UpdateFinalizeL1(ref eth.BlockRef) error - - // Getters for the latest safety status of each chain - UnsafeL2(chainID types.ChainID) (heads.HeadPointer, error) - CrossUnsafeL2(chainID types.ChainID) (heads.HeadPointer, error) - LocalSafeL2(chainID types.ChainID) (heads.HeadPointer, error) - CrossSafeL2(chainID types.ChainID) (heads.HeadPointer, error) - // We only finalize on full L2 block boundaries, hence not a heads.HeadPointer return. - FinalizedL2(chainId types.ChainID) (eth.BlockID, error) -} - -type ChainsDBClient interface { - IteratorStartingAt(chainID types.ChainID, sealedNum uint64, logIndex uint32) (logs.Iterator, error) - Check(chainID types.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (h common.Hash, err error) -} - -type safetyIndex struct { - log log.Logger - - chains ChainsDBClient - - unsafe map[types.ChainID]*View - safe map[types.ChainID]*View - finalized map[types.ChainID]eth.BlockID - - // remember what each non-finalized L2 block is derived from - derivedFrom map[types.ChainID]map[common.Hash]eth.BlockRef - - // the last received L1 finality signal. - finalizedL1 eth.BlockRef -} - -func NewSafetyIndex(log log.Logger, chains ChainsDBClient) *safetyIndex { - return &safetyIndex{ - log: log, - chains: chains, - unsafe: make(map[types.ChainID]*View), - safe: make(map[types.ChainID]*View), - finalized: make(map[types.ChainID]eth.BlockID), - derivedFrom: make(map[types.ChainID]map[common.Hash]eth.BlockRef), - } -} - -// UpdateLocalUnsafe updates the local-unsafe view for the given chain, and advances the cross-unsafe status. -func (r *safetyIndex) UpdateLocalUnsafe(chainID types.ChainID, ref eth.BlockRef) error { - view, ok := r.safe[chainID] - if !ok { - iter, err := r.chains.IteratorStartingAt(chainID, ref.Number, 0) - if err != nil { - return fmt.Errorf("failed to open iterator for chain %s block %d", chainID, ref.Number) - } - view = &View{ - chainID: chainID, - iter: iter, - localView: heads.HeadPointer{ - LastSealedBlockHash: ref.Hash, - LastSealedBlockNum: ref.Number, - LastSealedTimestamp: ref.Time, - LogsSince: 0, - }, - localDerivedFrom: eth.BlockRef{}, - validWithinView: r.ValidWithinUnsafeView, - } - r.unsafe[chainID] = view - } else if err := view.UpdateLocal(eth.BlockRef{}, ref); err != nil { - return fmt.Errorf("failed to update local-unsafe: %w", err) - } - local, _ := r.unsafe[chainID].Local() - r.log.Debug("Updated local unsafe head", "chainID", chainID, "local", local) - r.advanceCrossUnsafe() - return nil -} - -// advanceCrossUnsafe calls Process on all cross-unsafe views. -func (r *safetyIndex) advanceCrossUnsafe() { - for chainID, view := range r.unsafe { - if err := view.Process(); err != nil { - r.log.Error("Failed to update cross-unsafe view", "chain", chainID, "err", err) - } - cross, _ := r.unsafe[chainID].Cross() - r.log.Debug("Updated cross unsafe head", "chainID", chainID, "cross", cross) - } -} - -// UpdateLocalSafe updates the local-safe view for the given chain, and advances the cross-safe status. -func (r *safetyIndex) UpdateLocalSafe( - chainID types.ChainID, at eth.BlockRef, ref eth.BlockRef) error { - view, ok := r.safe[chainID] - if !ok { - iter, err := r.chains.IteratorStartingAt(chainID, ref.Number, 0) - if err != nil { - return fmt.Errorf("failed to open iterator for chain %s block %d", chainID, ref.Number) - } - view = &View{ - chainID: chainID, - iter: iter, - localView: heads.HeadPointer{ - LastSealedBlockHash: ref.Hash, - LastSealedBlockNum: ref.Number, - LastSealedTimestamp: ref.Time, - LogsSince: 0, - }, - localDerivedFrom: at, - validWithinView: r.ValidWithinSafeView, - } - r.safe[chainID] = view - } else if err := view.UpdateLocal(at, ref); err != nil { - return fmt.Errorf("failed to update local-safe: %w", err) - } - - // register what this L2 block is derived from - m, ok := r.derivedFrom[chainID] - if !ok { - m = make(map[common.Hash]eth.BlockRef) - r.derivedFrom[chainID] = m - } - m[ref.Hash] = at - local, _ := r.safe[chainID].Local() - r.log.Debug("Updated local safe head", "chainID", chainID, "local", local) - r.advanceCrossSafe() - return nil -} - -// advanceCrossSafe calls Process on all cross-safe views, and advances the finalized safety status. -func (r *safetyIndex) advanceCrossSafe() { - for chainID, view := range r.safe { - if err := view.Process(); err != nil { - r.log.Error("Failed to update cross-safe view", "chain", chainID, "err", err) - } - cross, _ := r.safe[chainID].Cross() - r.log.Debug("Updated local safe head", "chainID", chainID, "cross", cross) - } - r.advanceFinalized() -} - -// UpdateFinalizeL1 updates the finalized L1 block, and advances the finalized safety status. -func (r *safetyIndex) UpdateFinalizeL1(ref eth.BlockRef) error { - if ref.Number <= r.finalizedL1.Number { - return fmt.Errorf("ignoring old L1 finality signal of %s, already have %s", ref, r.finalizedL1) - } - r.finalizedL1 = ref - r.log.Debug("Updated L1 finalized head", "L1finalized", ref) - r.advanceFinalized() - return nil -} - -// advanceFinalized should be called whenever the finalized L1 block, or the cross-safe history, changes. -// This then promotes the irreversible cross-safe L2 blocks to a finalized safety status. -func (r *safetyIndex) advanceFinalized() { - // Whatever was considered cross-safe at the finalized block-height can - // now be considered finalized, since the inputs have become irreversible. - for chainID, view := range r.safe { - crossSafe, err := view.Cross() - if err != nil { - r.log.Info("Failed to get cross-safe data, cannot finalize", "chain", chainID, "err", err) - continue - } - // TODO(#12184): we need to consider older cross-safe data, - // if we want to finalize something at all on longer lagging finality signal. - // Could consider just iterating over all derivedFrom contents? - l1Dep := r.derivedFrom[chainID][crossSafe.LastSealedBlockHash] - if l1Dep.Number < r.finalizedL1.Number { - r.finalized[chainID] = eth.BlockID{Hash: crossSafe.LastSealedBlockHash, Number: crossSafe.LastSealedBlockNum} - finalized := r.finalized[chainID] - r.log.Debug("Updated finalized head", "chainID", chainID, "finalized", finalized) - } - } -} - -// UnsafeL2 returns the latest unsafe L2 block of the given chain. -func (r *safetyIndex) UnsafeL2(chainID types.ChainID) (heads.HeadPointer, error) { - view, ok := r.unsafe[chainID] - if !ok { - return heads.HeadPointer{}, fmt.Errorf("no unsafe data for chain %s", chainID) - } - return view.Local() -} - -// CrossUnsafeL2 returns the latest cross-unsafe L2 block of the given chain. -func (r *safetyIndex) CrossUnsafeL2(chainID types.ChainID) (heads.HeadPointer, error) { - view, ok := r.unsafe[chainID] - if !ok { - return heads.HeadPointer{}, fmt.Errorf("no cross-unsafe data for chain %s", chainID) - } - return view.Cross() -} - -// LocalSafeL2 returns the latest local-safe L2 block of the given chain. -func (r *safetyIndex) LocalSafeL2(chainID types.ChainID) (heads.HeadPointer, error) { - view, ok := r.safe[chainID] - if !ok { - return heads.HeadPointer{}, fmt.Errorf("no local-safe data for chain %s", chainID) - } - return view.Local() -} - -// CrossSafeL2 returns the latest cross-safe L2 block of the given chain. -func (r *safetyIndex) CrossSafeL2(chainID types.ChainID) (heads.HeadPointer, error) { - view, ok := r.safe[chainID] - if !ok { - return heads.HeadPointer{}, fmt.Errorf("no cross-safe data for chain %s", chainID) - } - return view.Cross() -} - -// FinalizedL2 returns the latest finalized L2 block of the given chain. -func (r *safetyIndex) FinalizedL2(chainId types.ChainID) (eth.BlockID, error) { - finalized, ok := r.finalized[chainId] - if !ok { - return eth.BlockID{}, fmt.Errorf("not seen finalized data of chain %s at finalized L1 block %s", chainId, r.finalizedL1) - } - return finalized, nil -} - -// ValidWithinUnsafeView checks if the given executing message is in the database. -// unsafe view is meant to represent all of the database, and so no boundary checks are needed. -func (r *safetyIndex) ValidWithinUnsafeView(_ uint64, execMsg *types.ExecutingMessage) error { - execChainID := types.ChainIDFromUInt64(uint64(execMsg.Chain)) - _, err := r.chains.Check(execChainID, execMsg.BlockNum, execMsg.LogIdx, execMsg.Hash) - return err -} - -// ValidWithinSafeView checks if the given executing message is within the database, -// and within the L1 view of the caller. -func (r *safetyIndex) ValidWithinSafeView(l1View uint64, execMsg *types.ExecutingMessage) error { - execChainID := types.ChainIDFromUInt64(uint64(execMsg.Chain)) - - // Check that the initiating message, which was pulled in by the executing message, - // does indeed exist. And in which L2 block it exists (if any). - l2BlockHash, err := r.chains.Check(execChainID, execMsg.BlockNum, execMsg.LogIdx, execMsg.Hash) - if err != nil { - return err - } - // if the executing message falls within the execFinalized range, then nothing to check - execFinalized, ok := r.finalized[execChainID] - if ok && execFinalized.Number > execMsg.BlockNum { - return nil - } - // check if the L1 block of the executing message is known - execL1Block, ok := r.derivedFrom[execChainID][l2BlockHash] - if !ok { - return logs.ErrFuture // TODO(#12185) need to distinguish between same-data future, and new-data future - } - // check if the L1 block is within the view - if execL1Block.Number > l1View { - return fmt.Errorf("exec message depends on L2 block %s:%d, derived from L1 block %s, not within view yet: %w", - l2BlockHash, execMsg.BlockNum, execL1Block, logs.ErrFuture) - } - return nil -} - -var _ SafetyIndex = (*safetyIndex)(nil) diff --git a/op-supervisor/supervisor/backend/safety/views.go b/op-supervisor/supervisor/backend/safety/views.go deleted file mode 100644 index e1c704fa260f3..0000000000000 --- a/op-supervisor/supervisor/backend/safety/views.go +++ /dev/null @@ -1,91 +0,0 @@ -package safety - -import ( - "errors" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -type View struct { - chainID types.ChainID - - iter logs.Iterator - - localView heads.HeadPointer - localDerivedFrom eth.BlockRef - - validWithinView func(l1View uint64, execMsg *types.ExecutingMessage) error -} - -func (vi *View) Cross() (heads.HeadPointer, error) { - return vi.iter.HeadPointer() -} - -func (vi *View) Local() (heads.HeadPointer, error) { - if vi.localView == (heads.HeadPointer{}) { - return heads.HeadPointer{}, logs.ErrFuture - } - return vi.localView, nil -} - -func (vi *View) UpdateLocal(at eth.BlockRef, ref eth.BlockRef) error { - vi.localView = heads.HeadPointer{ - LastSealedBlockHash: ref.Hash, - LastSealedBlockNum: ref.Number, - //LastSealedTimestamp: ref.Time, - LogsSince: 0, - } - vi.localDerivedFrom = at - - // TODO(#11693): reorg check against existing DB - // TODO(#12186): localView may be larger than what DB contents we have - return nil -} - -func (vi *View) Process() error { - err := vi.iter.TraverseConditional(func(state logs.IteratorState) error { - hash, num, ok := state.SealedBlock() - if !ok { - return logs.ErrFuture // maybe a more specific error for no-genesis case? - } - // TODO(#11693): reorg check in the future. To make sure that what we traverse is still canonical. - _ = hash - // check if L2 block is within view - if !vi.localView.WithinRange(num, 0) { - return logs.ErrFuture - } - _, initLogIndex, ok := state.InitMessage() - if !ok { - return nil // no readable message, just an empty block - } - // check if the message is within view - if !vi.localView.WithinRange(num, initLogIndex) { - return logs.ErrFuture - } - // check if it is an executing message. If so, check the dependency - if execMsg := state.ExecMessage(); execMsg != nil { - // Check if executing message is within cross L2 view, - // relative to the L1 view of current message. - // And check if the message is valid to execute at all - // (i.e. if it exists on the initiating side). - // TODO(#12187): it's inaccurate to check with the view of the local-unsafe - // it should be limited to the L1 view at the time of the inclusion of execution of the message. - err := vi.validWithinView(vi.localDerivedFrom.Number, execMsg) - if err != nil { - return err - } - } - return nil - }) - if err == nil { - panic("expected reader to complete with an exit-error") - } - if errors.Is(err, logs.ErrFuture) { - // register the new cross-safe block as cross-safe up to the current L1 view - return nil - } - return err -} diff --git a/op-supervisor/supervisor/backend/source/chain.go b/op-supervisor/supervisor/backend/source/chain.go deleted file mode 100644 index 383a5fb74de87..0000000000000 --- a/op-supervisor/supervisor/backend/source/chain.go +++ /dev/null @@ -1,84 +0,0 @@ -package source - -import ( - "context" - "fmt" - "time" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum-optimism/optimism/op-service/sources/caching" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -// TODO(optimism#11032) Make these configurable and a sensible default -const epochPollInterval = 3 * time.Second -const pollInterval = 2 * time.Second -const trustRpc = false -const rpcKind = sources.RPCKindStandard - -type Metrics interface { - caching.Metrics -} - -type Storage interface { - ChainsDBClientForLogProcessor - DatabaseRewinder - LatestBlockNum(chainID types.ChainID) (num uint64, ok bool) -} - -// ChainMonitor monitors a source L2 chain, retrieving the data required to populate the database and perform -// interop consolidation. It detects and notifies when reorgs occur. -type ChainMonitor struct { - log log.Logger - headMonitor *HeadMonitor - chainProcessor *ChainProcessor -} - -func NewChainMonitor(ctx context.Context, logger log.Logger, m Metrics, chainID types.ChainID, rpc string, client client.RPC, store Storage) (*ChainMonitor, error) { - logger = logger.New("chainID", chainID) - cl, err := newClient(ctx, logger, m, rpc, client, pollInterval, trustRpc, rpcKind) - if err != nil { - return nil, err - } - - // Create the log processor and fetcher - processLogs := newLogProcessor(chainID, store) - unsafeBlockProcessor := NewChainProcessor(logger, cl, chainID, processLogs, store) - - unsafeProcessors := []HeadProcessor{unsafeBlockProcessor} - - callback := newHeadUpdateProcessor(logger, unsafeProcessors, nil, nil) - headMonitor := NewHeadMonitor(logger, epochPollInterval, cl, callback) - - return &ChainMonitor{ - log: logger, - headMonitor: headMonitor, - chainProcessor: unsafeBlockProcessor, - }, nil -} - -func (c *ChainMonitor) Start() error { - c.log.Info("Started monitoring chain") - return c.headMonitor.Start() -} - -func (c *ChainMonitor) Stop() error { - c.chainProcessor.Close() - return c.headMonitor.Stop() -} - -func newClient(ctx context.Context, logger log.Logger, m caching.Metrics, rpc string, rpcClient client.RPC, pollRate time.Duration, trustRPC bool, kind sources.RPCProviderKind) (*sources.L1Client, error) { - c, err := client.NewRPCWithClient(ctx, logger, rpc, rpcClient, pollRate) - if err != nil { - return nil, fmt.Errorf("failed to create new RPC client: %w", err) - } - - l1Client, err := sources.NewL1Client(c, logger, m, sources.L1ClientSimpleConfig(trustRPC, kind, 100)) - if err != nil { - return nil, fmt.Errorf("failed to connect client: %w", err) - } - return l1Client, nil -} diff --git a/op-supervisor/supervisor/backend/source/chain_processor.go b/op-supervisor/supervisor/backend/source/chain_processor.go deleted file mode 100644 index 9c63950a1629b..0000000000000 --- a/op-supervisor/supervisor/backend/source/chain_processor.go +++ /dev/null @@ -1,188 +0,0 @@ -package source - -import ( - "context" - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -type Source interface { - L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) - FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, gethtypes.Receipts, error) -} - -type LogProcessor interface { - ProcessLogs(ctx context.Context, block eth.BlockRef, receipts gethtypes.Receipts) error -} - -type DatabaseRewinder interface { - Rewind(chain types.ChainID, headBlockNum uint64) error - LatestBlockNum(chain types.ChainID) (num uint64, ok bool) -} - -type BlockProcessorFn func(ctx context.Context, block eth.BlockRef) error - -func (fn BlockProcessorFn) ProcessBlock(ctx context.Context, block eth.BlockRef) error { - return fn(ctx, block) -} - -// ChainProcessor is a HeadProcessor that fills in any skipped blocks between head update events. -// It ensures that, absent reorgs, every block in the chain is processed even if some head advancements are skipped. -type ChainProcessor struct { - log log.Logger - client Source - - chain types.ChainID - - processor LogProcessor - rewinder DatabaseRewinder - - // the last known head. May be 0 if not known. - lastHead atomic.Uint64 - // channel with capacity of 1, full if there is work to do - newHead chan struct{} - - // bool to indicate if calls are synchronous - synchronous bool - // channel with capacity of 1, to signal work complete if running in synchroneous mode - out chan struct{} - - // lifetime management of the chain processor - ctx context.Context - cancel context.CancelFunc - wg sync.WaitGroup -} - -func NewChainProcessor(log log.Logger, client Source, chain types.ChainID, processor LogProcessor, rewinder DatabaseRewinder) *ChainProcessor { - ctx, cancel := context.WithCancel(context.Background()) - out := &ChainProcessor{ - log: log, - client: client, - chain: chain, - processor: processor, - rewinder: rewinder, - newHead: make(chan struct{}, 1), - // default to synchronous because we want other processors to wait for this - // in the future we could make this async and have a separate mechanism which forwards the work signal to other processors - synchronous: true, - out: make(chan struct{}, 1), - ctx: ctx, - cancel: cancel, - } - out.wg.Add(1) - go out.worker() - return out -} - -func (s *ChainProcessor) nextNum() uint64 { - headNum, ok := s.rewinder.LatestBlockNum(s.chain) - if !ok { - return 0 // genesis. We could change this to start at a later block. - } - return headNum + 1 -} - -func (s *ChainProcessor) worker() { - defer s.wg.Done() - - delay := time.NewTicker(time.Second * 5) - for { - if s.ctx.Err() != nil { // check if we are closing down - return - } - target := s.nextNum() - if err := s.update(target); err != nil { - s.log.Error("Failed to process new block", "err", err) - // idle until next update trigger - } else if x := s.lastHead.Load(); target+1 <= x { - s.log.Debug("Continuing with next block", - "newTarget", target+1, "lastHead", x) - continue // instantly continue processing, no need to idle - } else { - s.log.Debug("Idling block-processing, reached latest block", "head", target) - } - if s.synchronous { - s.out <- struct{}{} - } - // await next time we process, or detect shutdown - select { - case <-s.ctx.Done(): - delay.Stop() - return - case <-s.newHead: - s.log.Debug("Responding to new head signal") - continue - case <-delay.C: - s.log.Debug("Checking for updates") - continue - } - } -} - -func (s *ChainProcessor) update(nextNum uint64) error { - ctx, cancel := context.WithTimeout(s.ctx, time.Second*10) - nextL1, err := s.client.L1BlockRefByNumber(ctx, nextNum) - next := eth.BlockRef{ - Hash: nextL1.Hash, - ParentHash: nextL1.ParentHash, - Number: nextL1.Number, - Time: nextL1.Time, - } - cancel() - if err != nil { - return fmt.Errorf("failed to fetch next block: %w", err) - } - - // Try and fetch the receipts - ctx, cancel = context.WithTimeout(s.ctx, time.Second*10) - _, receipts, err := s.client.FetchReceipts(ctx, next.Hash) - cancel() - if err != nil { - return fmt.Errorf("failed to fetch receipts of block: %w", err) - } - if err := s.processor.ProcessLogs(ctx, next, receipts); err != nil { - s.log.Error("Failed to process block", "block", next, "err", err) - - if next.Number == 0 { // cannot rewind genesis - return nil - } - - // Try to rewind the database to the previous block to remove any logs from this block that were written - if err := s.rewinder.Rewind(s.chain, nextNum-1); err != nil { - // If any logs were written, our next attempt to write will fail and we'll retry this rewind. - // If no logs were written successfully then the rewind wouldn't have done anything anyway. - s.log.Error("Failed to rewind after error processing block", "block", next, "err", err) - } - } - return nil -} - -func (s *ChainProcessor) OnNewHead(ctx context.Context, head eth.BlockRef) error { - // update the latest target - s.lastHead.Store(head.Number) - // signal that we have something to process - select { - case s.newHead <- struct{}{}: - default: - // already requested an update - } - // if we are running synchronously, wait for the work to complete - if s.synchronous { - <-s.out - } - return nil -} - -func (s *ChainProcessor) Close() { - s.cancel() - s.wg.Wait() -} diff --git a/op-supervisor/supervisor/backend/source/chain_processor_test.go b/op-supervisor/supervisor/backend/source/chain_processor_test.go deleted file mode 100644 index af48d5ecdd30b..0000000000000 --- a/op-supervisor/supervisor/backend/source/chain_processor_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package source - -/* TODO -import ( - "context" - "errors" - "fmt" - "testing" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" -) - -var processorChainID = types.ChainIDFromUInt64(4) - -func TestUnsafeBlocksStage(t *testing.T) { - t.Run("IgnoreEventsAtOrPriorToStartingHead", func(t *testing.T) { - ctx := context.Background() - logger := testlog.Logger(t, log.LvlInfo) - client := &stubBlockByNumberSource{} - processor := &stubBlockProcessor{} - stage := NewChainProcessor(logger, client, processorChainID, processor, &stubRewinder{}) - stage.OnNewHead(ctx, eth.L1BlockRef{Number: 100}) - stage.OnNewHead(ctx, eth.L1BlockRef{Number: 99}) - - require.Empty(t, processor.processed) - require.Zero(t, client.calls) - }) - - t.Run("OutputNewHeadsWithNoMissedBlocks", func(t *testing.T) { - ctx := context.Background() - logger := testlog.Logger(t, log.LvlInfo) - client := &stubBlockByNumberSource{} - block0 := eth.L1BlockRef{Number: 100} - block1 := eth.L1BlockRef{Number: 101} - block2 := eth.L1BlockRef{Number: 102} - block3 := eth.L1BlockRef{Number: 103} - processor := &stubBlockProcessor{} - stage := NewChainProcessor(logger, client, processorChainID, block0, processor, &stubRewinder{}) - stage.OnNewHead(ctx, block1) - require.Equal(t, []eth.L1BlockRef{block1}, processor.processed) - stage.OnNewHead(ctx, block2) - require.Equal(t, []eth.L1BlockRef{block1, block2}, processor.processed) - stage.OnNewHead(ctx, block3) - require.Equal(t, []eth.L1BlockRef{block1, block2, block3}, processor.processed) - - require.Zero(t, client.calls, "should not need to request block info") - }) - - t.Run("IgnoreEventsAtOrPriorToPreviousHead", func(t *testing.T) { - ctx := context.Background() - logger := testlog.Logger(t, log.LvlInfo) - client := &stubBlockByNumberSource{} - block0 := eth.L1BlockRef{Number: 100} - block1 := eth.L1BlockRef{Number: 101} - processor := &stubBlockProcessor{} - stage := NewChainProcessor(logger, client, processorChainID, block0, processor, &stubRewinder{}) - stage.OnNewHead(ctx, block1) - require.NotEmpty(t, processor.processed) - require.Equal(t, []eth.L1BlockRef{block1}, processor.processed) - - stage.OnNewHead(ctx, block0) - stage.OnNewHead(ctx, block1) - require.Equal(t, []eth.L1BlockRef{block1}, processor.processed) - - require.Zero(t, client.calls, "should not need to request block info") - }) - - t.Run("OutputSkippedBlocks", func(t *testing.T) { - ctx := context.Background() - logger := testlog.Logger(t, log.LvlInfo) - client := &stubBlockByNumberSource{} - block0 := eth.L1BlockRef{Number: 100} - block3 := eth.L1BlockRef{Number: 103} - processor := &stubBlockProcessor{} - stage := NewChainProcessor(logger, client, processorChainID, block0, processor, &stubRewinder{}) - - stage.OnNewHead(ctx, block3) - require.Equal(t, []eth.L1BlockRef{makeBlockRef(101), makeBlockRef(102), block3}, processor.processed) - - require.Equal(t, 2, client.calls, "should only request the two missing blocks") - }) - - t.Run("DoNotUpdateLastBlockOnFetchError", func(t *testing.T) { - ctx := context.Background() - logger := testlog.Logger(t, log.LvlInfo) - client := &stubBlockByNumberSource{err: errors.New("boom")} - block0 := eth.L1BlockRef{Number: 100} - block3 := eth.L1BlockRef{Number: 103} - processor := &stubBlockProcessor{} - rewinder := &stubRewinder{} - stage := NewChainProcessor(logger, client, processorChainID, block0, processor, rewinder) - - stage.OnNewHead(ctx, block3) - require.Empty(t, processor.processed, "should not update any blocks because backfill failed") - - client.err = nil - stage.OnNewHead(ctx, block3) - require.Equal(t, []eth.L1BlockRef{makeBlockRef(101), makeBlockRef(102), block3}, processor.processed) - require.False(t, rewinder.rewindCalled, "should not rewind because no logs could have been written") - }) - - t.Run("DoNotUpdateLastBlockOnProcessorError", func(t *testing.T) { - ctx := context.Background() - logger := testlog.Logger(t, log.LvlInfo) - client := &stubBlockByNumberSource{} - block0 := eth.L1BlockRef{Number: 100} - block3 := eth.L1BlockRef{Number: 103} - processor := &stubBlockProcessor{err: errors.New("boom")} - rewinder := &stubRewinder{} - stage := NewChainProcessor(logger, client, processorChainID, block0, processor, rewinder) - - stage.OnNewHead(ctx, block3) - require.Equal(t, []eth.L1BlockRef{makeBlockRef(101)}, processor.processed, "Attempted to process block 101") - require.Equal(t, block0.Number, rewinder.rewoundTo, "should rewind to block before error") - - processor.err = nil - stage.OnNewHead(ctx, block3) - // Attempts to process block 101 again, then carries on - require.Equal(t, []eth.L1BlockRef{makeBlockRef(101), makeBlockRef(101), makeBlockRef(102), block3}, processor.processed) - }) - - t.Run("RewindWhenNewHeadProcessingFails", func(t *testing.T) { - ctx := context.Background() - logger := testlog.Logger(t, log.LvlInfo) - client := &stubBlockByNumberSource{} - block0 := eth.L1BlockRef{Number: 100} - block1 := eth.L1BlockRef{Number: 101} - processor := &stubBlockProcessor{err: errors.New("boom")} - rewinder := &stubRewinder{} - stage := NewChainProcessor(logger, client, processorChainID, block0, processor, rewinder) - - // No skipped blocks - stage.OnNewHead(ctx, block1) - require.Equal(t, []eth.L1BlockRef{block1}, processor.processed, "Attempted to process block 101") - require.Equal(t, block0.Number, rewinder.rewoundTo, "should rewind to block before error") - }) -} - -type stubBlockByNumberSource struct { - calls int - err error -} - -func (s *stubBlockByNumberSource) L1BlockRefByNumber(_ context.Context, number uint64) (eth.L1BlockRef, error) { - s.calls++ - if s.err != nil { - return eth.L1BlockRef{}, s.err - } - return makeBlockRef(number), nil -} - -type stubBlockProcessor struct { - processed []eth.L1BlockRef - err error -} - -func (s *stubBlockProcessor) ProcessBlock(_ context.Context, block eth.L1BlockRef) error { - s.processed = append(s.processed, block) - return s.err -} - -func makeBlockRef(number uint64) eth.L1BlockRef { - return eth.L1BlockRef{ - Number: number, - Hash: common.Hash{byte(number)}, - ParentHash: common.Hash{byte(number - 1)}, - Time: number * 1000, - } -} - -type stubRewinder struct { - rewoundTo uint64 - rewindCalled bool -} - -func (s *stubRewinder) Rewind(chainID types.ChainID, headBlockNum uint64) error { - if chainID != processorChainID { - return fmt.Errorf("chainID mismatch, expected %v but was %v", processorChainID, chainID) - } - s.rewoundTo = headBlockNum - s.rewindCalled = true - return nil -} -*/ diff --git a/op-supervisor/supervisor/backend/source/contracts/l2inbox.go b/op-supervisor/supervisor/backend/source/contracts/l2inbox.go deleted file mode 100644 index 741c6ae3d8659..0000000000000 --- a/op-supervisor/supervisor/backend/source/contracts/l2inbox.go +++ /dev/null @@ -1,133 +0,0 @@ -package contracts - -import ( - "bytes" - "errors" - "fmt" - "io" - "math/big" - - "github.com/ethereum-optimism/optimism/op-service/predeploys" - "github.com/ethereum-optimism/optimism/op-service/solabi" - "github.com/ethereum-optimism/optimism/op-service/sources/batching" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" - "github.com/ethereum/go-ethereum/common" - ethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" -) - -const ( - eventExecutingMessage = "ExecutingMessage" -) - -var ( - ErrEventNotFound = errors.New("event not found") -) - -type contractIdentifier struct { - // Origin represents the address that initiated the message - // it is used in combination with the MsgHash to uniquely identify a message - // and is hashed into the log hash, not stored directly. - Origin common.Address - LogIndex *big.Int - BlockNumber *big.Int - ChainId *big.Int - Timestamp *big.Int -} - -type CrossL2Inbox struct { - contract *batching.BoundContract -} - -func NewCrossL2Inbox() *CrossL2Inbox { - abi := snapshots.LoadCrossL2InboxABI() - return &CrossL2Inbox{ - contract: batching.NewBoundContract(abi, predeploys.CrossL2InboxAddr), - } -} - -func (i *CrossL2Inbox) DecodeExecutingMessageLog(l *ethTypes.Log) (types.ExecutingMessage, error) { - if l.Address != i.contract.Addr() { - return types.ExecutingMessage{}, fmt.Errorf("%w: log not from CrossL2Inbox", ErrEventNotFound) - } - // use DecodeEvent to check the name of the event - // but the actual decoding is done manually to extract the contract identifier - name, _, err := i.contract.DecodeEvent(l) - if errors.Is(err, batching.ErrUnknownEvent) { - return types.ExecutingMessage{}, fmt.Errorf("%w: %v", ErrEventNotFound, err.Error()) - } else if err != nil { - return types.ExecutingMessage{}, fmt.Errorf("failed to decode event: %w", err) - } - if name != eventExecutingMessage { - return types.ExecutingMessage{}, fmt.Errorf("%w: event %v not an ExecutingMessage event", ErrEventNotFound, name) - } - // the second topic is the hash of the payload (the first is the event ID) - msgHash := l.Topics[1] - // the first 32 bytes of the data are the msgHash, so we skip them - identifierBytes := bytes.NewReader(l.Data[32:]) - identifier, err := identifierFromBytes(identifierBytes) - if err != nil { - return types.ExecutingMessage{}, fmt.Errorf("failed to read contract identifier: %w", err) - } - chainID, err := types.ChainIDFromBig(identifier.ChainId).ToUInt32() - if err != nil { - return types.ExecutingMessage{}, fmt.Errorf("failed to convert chain ID %v to uint32: %w", identifier.ChainId, err) - } - hash := payloadHashToLogHash(msgHash, identifier.Origin) - return types.ExecutingMessage{ - Chain: chainID, - Hash: hash, - BlockNum: identifier.BlockNumber.Uint64(), - LogIdx: uint32(identifier.LogIndex.Uint64()), - Timestamp: identifier.Timestamp.Uint64(), - }, nil -} - -// identifierFromBytes reads a contract identifier from a byte stream. -// it follows the spec and matches the CrossL2Inbox.json definition, -// rather than relying on reflection, as that can be error-prone regarding struct ordering -func identifierFromBytes(identifierBytes io.Reader) (contractIdentifier, error) { - origin, err := solabi.ReadAddress(identifierBytes) - if err != nil { - return contractIdentifier{}, fmt.Errorf("failed to read origin address: %w", err) - } - originAddr := common.BytesToAddress(origin[:]) - blockNumber, err := solabi.ReadUint256(identifierBytes) - if err != nil { - return contractIdentifier{}, fmt.Errorf("failed to read block number: %w", err) - } - logIndex, err := solabi.ReadUint256(identifierBytes) - if err != nil { - return contractIdentifier{}, fmt.Errorf("failed to read log index: %w", err) - } - timestamp, err := solabi.ReadUint256(identifierBytes) - if err != nil { - return contractIdentifier{}, fmt.Errorf("failed to read timestamp: %w", err) - } - chainID, err := solabi.ReadUint256(identifierBytes) - if err != nil { - return contractIdentifier{}, fmt.Errorf("failed to read chain ID: %w", err) - } - return contractIdentifier{ - Origin: originAddr, - BlockNumber: blockNumber, - LogIndex: logIndex, - Timestamp: timestamp, - ChainId: chainID, - }, nil -} - -// payloadHashToLogHash converts the payload hash to the log hash -// it is the concatenation of the log's address and the hash of the log's payload, -// which is then hashed again. This is the hash that is stored in the log storage. -// The logHash can then be used to traverse from the executing message -// to the log the referenced initiating message. -// TODO: this function is duplicated between contracts and backend/source/log_processor.go -// to avoid a circular dependency. It should be reorganized to avoid this duplication. -func payloadHashToLogHash(payloadHash common.Hash, addr common.Address) common.Hash { - msg := make([]byte, 0, 2*common.HashLength) - msg = append(msg, addr.Bytes()...) - msg = append(msg, payloadHash.Bytes()...) - return crypto.Keccak256Hash(msg) -} diff --git a/op-supervisor/supervisor/backend/source/contracts/l2inbox_test.go b/op-supervisor/supervisor/backend/source/contracts/l2inbox_test.go deleted file mode 100644 index 302b188e5cdf7..0000000000000 --- a/op-supervisor/supervisor/backend/source/contracts/l2inbox_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package contracts - -import ( - "bytes" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/op-service/predeploys" - "github.com/ethereum-optimism/optimism/op-service/sources/batching" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" - "github.com/ethereum/go-ethereum/common" - ethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/require" -) - -func TestDecodeExecutingMessageEvent(t *testing.T) { - inbox := NewCrossL2Inbox() - payload := bytes.Repeat([]byte{0xaa, 0xbb}, 50) - payloadHash := crypto.Keccak256Hash(payload) - expected := types.ExecutingMessage{ - Chain: 42424, - BlockNum: 12345, - LogIdx: 98, - Timestamp: 9578295, - } - contractIdent := contractIdentifier{ - Origin: common.Address{0xbb, 0xcc}, - ChainId: new(big.Int).SetUint64(uint64(expected.Chain)), - BlockNumber: new(big.Int).SetUint64(expected.BlockNum), - Timestamp: new(big.Int).SetUint64(expected.Timestamp), - LogIndex: new(big.Int).SetUint64(uint64(expected.LogIdx)), - } - expected.Hash = payloadHashToLogHash(payloadHash, contractIdent.Origin) - abi := snapshots.LoadCrossL2InboxABI() - validData, err := abi.Events[eventExecutingMessage].Inputs.Pack(payloadHash, contractIdent) - require.NoError(t, err) - createValidLog := func() *ethTypes.Log { - //protoHack := bytes.Repeat([]byte{0x00}, 32*5) - return ðTypes.Log{ - Address: predeploys.CrossL2InboxAddr, - Topics: []common.Hash{abi.Events[eventExecutingMessage].ID, payloadHash}, - Data: validData, - } - } - - t.Run("ParseValid", func(t *testing.T) { - l := createValidLog() - result, err := inbox.DecodeExecutingMessageLog(l) - require.NoError(t, err) - require.Equal(t, expected, result) - }) - - t.Run("IgnoreIncorrectContract", func(t *testing.T) { - l := createValidLog() - l.Address = common.Address{0xff} - _, err := inbox.DecodeExecutingMessageLog(l) - require.ErrorIs(t, err, ErrEventNotFound) - }) - - t.Run("IgnoreWrongEvent", func(t *testing.T) { - l := createValidLog() - l.Topics[0] = common.Hash{0xbb} - _, err := inbox.DecodeExecutingMessageLog(l) - require.ErrorIs(t, err, ErrEventNotFound) - }) - - t.Run("ErrorOnInvalidEvent", func(t *testing.T) { - l := createValidLog() - l.Data = []byte{0xbb, 0xcc} - _, err := inbox.DecodeExecutingMessageLog(l) - require.ErrorIs(t, err, batching.ErrInvalidEvent) - }) -} diff --git a/op-supervisor/supervisor/backend/source/head_monitor.go b/op-supervisor/supervisor/backend/source/head_monitor.go deleted file mode 100644 index f5c8896693fd5..0000000000000 --- a/op-supervisor/supervisor/backend/source/head_monitor.go +++ /dev/null @@ -1,97 +0,0 @@ -package source - -import ( - "context" - "errors" - "sync/atomic" - "time" - - "github.com/ethereum-optimism/optimism/op-service/eth" - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" -) - -type HeadMonitorClient interface { - eth.NewHeadSource - eth.L1BlockRefsSource -} - -type HeadChangeCallback interface { - OnNewUnsafeHead(ctx context.Context, block eth.L1BlockRef) - OnNewSafeHead(ctx context.Context, block eth.L1BlockRef) - OnNewFinalizedHead(ctx context.Context, block eth.L1BlockRef) -} - -// HeadMonitor monitors an L2 chain and sends notifications when the unsafe, safe or finalized head changes. -// Head updates may be coalesced, allowing the head block to skip forward multiple blocks. -// Reorgs are not identified. -type HeadMonitor struct { - log log.Logger - epochPollInterval time.Duration - rpc HeadMonitorClient - callback HeadChangeCallback - - started atomic.Bool - headsSub event.Subscription - safeSub ethereum.Subscription - finalizedSub ethereum.Subscription -} - -func NewHeadMonitor(logger log.Logger, epochPollInterval time.Duration, rpc HeadMonitorClient, callback HeadChangeCallback) *HeadMonitor { - return &HeadMonitor{ - log: logger, - epochPollInterval: epochPollInterval, - rpc: rpc, - callback: callback, - } -} - -func (h *HeadMonitor) Start() error { - if !h.started.CompareAndSwap(false, true) { - return errors.New("already started") - } - - // Keep subscribed to the unsafe head, which changes frequently. - h.headsSub = event.ResubscribeErr(time.Second*10, func(ctx context.Context, err error) (event.Subscription, error) { - if err != nil { - h.log.Warn("Resubscribing after failed heads subscription", "err", err) - } - return eth.WatchHeadChanges(ctx, h.rpc, h.callback.OnNewUnsafeHead) - }) - go func() { - err, ok := <-h.headsSub.Err() - if !ok { - return - } - h.log.Error("Heads subscription error", "err", err) - }() - - // Poll for the safe block and finalized block, which only change once per epoch at most and may be delayed. - h.safeSub = eth.PollBlockChanges(h.log, h.rpc, h.callback.OnNewSafeHead, eth.Safe, - h.epochPollInterval, time.Second*10) - h.finalizedSub = eth.PollBlockChanges(h.log, h.rpc, h.callback.OnNewFinalizedHead, eth.Finalized, - h.epochPollInterval, time.Second*10) - h.log.Info("Chain head monitoring started") - return nil -} - -func (h *HeadMonitor) Stop() error { - if !h.started.CompareAndSwap(true, false) { - return errors.New("already stopped") - } - - // stop heads feed - if h.headsSub != nil { - h.headsSub.Unsubscribe() - } - // stop polling for safe-head changes - if h.safeSub != nil { - h.safeSub.Unsubscribe() - } - // stop polling for finalized-head changes - if h.finalizedSub != nil { - h.finalizedSub.Unsubscribe() - } - return nil -} diff --git a/op-supervisor/supervisor/backend/source/head_monitor_test.go b/op-supervisor/supervisor/backend/source/head_monitor_test.go deleted file mode 100644 index d13dff48d851c..0000000000000 --- a/op-supervisor/supervisor/backend/source/head_monitor_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package source - -import ( - "context" - "errors" - "fmt" - "math/rand" - "sync" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum-optimism/optimism/op-service/testutils" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" -) - -const waitDuration = 10 * time.Second -const checkInterval = 10 * time.Millisecond - -func TestUnsafeHeadUpdates(t *testing.T) { - rng := rand.New(rand.NewSource(0x1337)) - header1 := testutils.RandomHeader(rng) - header2 := testutils.RandomHeader(rng) - - t.Run("NotifyOfNewHeads", func(t *testing.T) { - rpc, callback := startHeadMonitor(t) - - rpc.NewUnsafeHead(t, header1) - callback.RequireUnsafeHeaders(t, header1) - - rpc.NewUnsafeHead(t, header2) - callback.RequireUnsafeHeaders(t, header1, header2) - }) - - t.Run("ResubscribeOnError", func(t *testing.T) { - rpc, callback := startHeadMonitor(t) - - rpc.SubscriptionError(t) - - rpc.NewUnsafeHead(t, header1) - callback.RequireUnsafeHeaders(t, header1) - }) -} - -func TestSafeHeadUpdates(t *testing.T) { - rpc, callback := startHeadMonitor(t) - - head1 := eth.L1BlockRef{ - Hash: common.Hash{0xaa}, - Number: 1, - } - head2 := eth.L1BlockRef{ - Hash: common.Hash{0xbb}, - Number: 2, - } - - rpc.SetSafeHead(head1) - callback.RequireSafeHeaders(t, head1) - rpc.SetSafeHead(head2) - callback.RequireSafeHeaders(t, head1, head2) -} - -func TestFinalizedHeadUpdates(t *testing.T) { - rpc, callback := startHeadMonitor(t) - - head1 := eth.L1BlockRef{ - Hash: common.Hash{0xaa}, - Number: 1, - } - head2 := eth.L1BlockRef{ - Hash: common.Hash{0xbb}, - Number: 2, - } - - rpc.SetFinalizedHead(head1) - callback.RequireFinalizedHeaders(t, head1) - rpc.SetFinalizedHead(head2) - callback.RequireFinalizedHeaders(t, head1, head2) -} - -func startHeadMonitor(t *testing.T) (*stubRPC, *stubCallback) { - logger := testlog.Logger(t, log.LvlInfo) - rpc := &stubRPC{} - callback := &stubCallback{} - monitor := NewHeadMonitor(logger, 50*time.Millisecond, rpc, callback) - require.NoError(t, monitor.Start()) - t.Cleanup(func() { - require.NoError(t, monitor.Stop()) - }) - return rpc, callback -} - -type stubCallback struct { - sync.Mutex - unsafe []eth.L1BlockRef - safe []eth.L1BlockRef - finalized []eth.L1BlockRef -} - -func (s *stubCallback) RequireUnsafeHeaders(t *testing.T, heads ...*types.Header) { - expected := make([]eth.L1BlockRef, len(heads)) - for i, head := range heads { - expected[i] = eth.InfoToL1BlockRef(eth.HeaderBlockInfo(head)) - } - s.requireHeaders(t, func(s *stubCallback) []eth.L1BlockRef { return s.unsafe }, expected) -} - -func (s *stubCallback) RequireSafeHeaders(t *testing.T, expected ...eth.L1BlockRef) { - s.requireHeaders(t, func(s *stubCallback) []eth.L1BlockRef { return s.safe }, expected) -} - -func (s *stubCallback) RequireFinalizedHeaders(t *testing.T, expected ...eth.L1BlockRef) { - s.requireHeaders(t, func(s *stubCallback) []eth.L1BlockRef { return s.finalized }, expected) -} - -func (s *stubCallback) requireHeaders(t *testing.T, getter func(*stubCallback) []eth.L1BlockRef, expected []eth.L1BlockRef) { - require.Eventually(t, func() bool { - s.Lock() - defer s.Unlock() - return len(getter(s)) >= len(expected) - }, waitDuration, checkInterval) - s.Lock() - defer s.Unlock() - require.Equal(t, expected, getter(s)) -} - -func (s *stubCallback) OnNewUnsafeHead(ctx context.Context, block eth.L1BlockRef) { - s.Lock() - defer s.Unlock() - s.unsafe = append(s.unsafe, block) -} - -func (s *stubCallback) OnNewSafeHead(ctx context.Context, block eth.L1BlockRef) { - s.Lock() - defer s.Unlock() - s.safe = append(s.safe, block) -} - -func (s *stubCallback) OnNewFinalizedHead(ctx context.Context, block eth.L1BlockRef) { - s.Lock() - defer s.Unlock() - s.finalized = append(s.finalized, block) -} - -var _ HeadChangeCallback = (*stubCallback)(nil) - -type stubRPC struct { - sync.Mutex - sub *mockSubscription - - safeHead eth.L1BlockRef - finalizedHead eth.L1BlockRef -} - -func (s *stubRPC) SubscribeNewHead(_ context.Context, unsafeCh chan<- *types.Header) (ethereum.Subscription, error) { - s.Lock() - defer s.Unlock() - if s.sub != nil { - return nil, errors.New("already subscribed to unsafe heads") - } - errChan := make(chan error) - s.sub = &mockSubscription{errChan, unsafeCh, s} - return s.sub, nil -} - -func (s *stubRPC) SetSafeHead(head eth.L1BlockRef) { - s.Lock() - defer s.Unlock() - s.safeHead = head -} - -func (s *stubRPC) SetFinalizedHead(head eth.L1BlockRef) { - s.Lock() - defer s.Unlock() - s.finalizedHead = head -} - -func (s *stubRPC) L1BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L1BlockRef, error) { - s.Lock() - defer s.Unlock() - switch label { - case eth.Safe: - if s.safeHead == (eth.L1BlockRef{}) { - return eth.L1BlockRef{}, errors.New("no unsafe head") - } - return s.safeHead, nil - case eth.Finalized: - if s.finalizedHead == (eth.L1BlockRef{}) { - return eth.L1BlockRef{}, errors.New("no finalized head") - } - return s.finalizedHead, nil - default: - return eth.L1BlockRef{}, fmt.Errorf("unknown label: %v", label) - } -} - -func (s *stubRPC) NewUnsafeHead(t *testing.T, header *types.Header) { - s.WaitForSub(t) - s.Lock() - defer s.Unlock() - require.NotNil(t, s.sub, "Attempting to publish a header with no subscription") - s.sub.headers <- header -} - -func (s *stubRPC) SubscriptionError(t *testing.T) { - s.WaitForSub(t) - s.Lock() - defer s.Unlock() - s.sub.errChan <- errors.New("subscription error") - s.sub = nil -} - -func (s *stubRPC) WaitForSub(t *testing.T) { - require.Eventually(t, func() bool { - s.Lock() - defer s.Unlock() - return s.sub != nil - }, waitDuration, checkInterval, "Head monitor did not subscribe to unsafe head") -} - -var _ HeadMonitorClient = (*stubRPC)(nil) - -type mockSubscription struct { - errChan chan error - headers chan<- *types.Header - rpc *stubRPC -} - -func (m *mockSubscription) Unsubscribe() { - fmt.Println("Unsubscribed") - m.rpc.Lock() - defer m.rpc.Unlock() - m.rpc.sub = nil -} - -func (m *mockSubscription) Err() <-chan error { - return m.errChan -} diff --git a/op-supervisor/supervisor/backend/source/head_processor.go b/op-supervisor/supervisor/backend/source/head_processor.go deleted file mode 100644 index 6a0f867ac61aa..0000000000000 --- a/op-supervisor/supervisor/backend/source/head_processor.go +++ /dev/null @@ -1,76 +0,0 @@ -package source - -import ( - "context" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -type HeadProcessor interface { - OnNewHead(ctx context.Context, head eth.L1BlockRef) error -} - -type HeadProcessorFn func(ctx context.Context, head eth.L1BlockRef) error - -func (f HeadProcessorFn) OnNewHead(ctx context.Context, head eth.L1BlockRef) error { - return f(ctx, head) -} - -// headUpdateProcessor handles head update events and routes them to the appropriate handlers -type headUpdateProcessor struct { - log log.Logger - unsafeProcessors []HeadProcessor - safeProcessors []HeadProcessor - finalizedProcessors []HeadProcessor -} - -func newHeadUpdateProcessor(log log.Logger, unsafeProcessors []HeadProcessor, safeProcessors []HeadProcessor, finalizedProcessors []HeadProcessor) *headUpdateProcessor { - return &headUpdateProcessor{ - log: log, - unsafeProcessors: unsafeProcessors, - safeProcessors: safeProcessors, - finalizedProcessors: finalizedProcessors, - } -} - -func (n *headUpdateProcessor) OnNewUnsafeHead(ctx context.Context, block eth.L1BlockRef) { - n.log.Debug("New unsafe head", "block", block) - for _, processor := range n.unsafeProcessors { - if err := processor.OnNewHead(ctx, block); err != nil { - n.log.Error("unsafe-head processing failed", "err", err) - } - } -} - -func (n *headUpdateProcessor) OnNewSafeHead(ctx context.Context, block eth.L1BlockRef) { - n.log.Debug("New safe head", "block", block) - for _, processor := range n.safeProcessors { - if err := processor.OnNewHead(ctx, block); err != nil { - n.log.Error("safe-head processing failed", "err", err) - } - } -} - -func (n *headUpdateProcessor) OnNewFinalizedHead(ctx context.Context, block eth.L1BlockRef) { - n.log.Debug("New finalized head", "block", block) - for _, processor := range n.finalizedProcessors { - if err := processor.OnNewHead(ctx, block); err != nil { - n.log.Error("finalized-head processing failed", "err", err) - } - } -} - -// OnNewHead is a util function to turn a head-signal processor into head-pointer updater -func OnNewHead(id types.ChainID, apply func(id types.ChainID, v heads.HeadPointer) error) HeadProcessorFn { - return func(ctx context.Context, head eth.L1BlockRef) error { - return apply(id, heads.HeadPointer{ - LastSealedBlockHash: head.Hash, - LastSealedBlockNum: head.Number, - LogsSince: 0, - }) - } -} diff --git a/op-supervisor/supervisor/backend/source/head_processor_test.go b/op-supervisor/supervisor/backend/source/head_processor_test.go deleted file mode 100644 index f684667fa62b8..0000000000000 --- a/op-supervisor/supervisor/backend/source/head_processor_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package source - -import ( - "context" - "testing" - - "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/log" - "github.com/stretchr/testify/require" -) - -func TestHeadUpdateProcessor(t *testing.T) { - t.Run("NotifyUnsafeHeadProcessors", func(t *testing.T) { - logger := testlog.Logger(t, log.LvlInfo) - processed := make([]eth.L1BlockRef, 3) - makeProcessor := func(idx int) HeadProcessor { - return HeadProcessorFn(func(_ context.Context, head eth.L1BlockRef) error { - processed[idx] = head - return nil - }) - } - headUpdates := newHeadUpdateProcessor(logger, []HeadProcessor{makeProcessor(0), makeProcessor(1), makeProcessor(2)}, nil, nil) - block := eth.L1BlockRef{Number: 110, Hash: common.Hash{0xaa}} - headUpdates.OnNewUnsafeHead(context.Background(), block) - require.Equal(t, []eth.L1BlockRef{block, block, block}, processed) - }) - - t.Run("NotifySafeHeadProcessors", func(t *testing.T) { - logger := testlog.Logger(t, log.LvlInfo) - processed := make([]eth.L1BlockRef, 3) - makeProcessor := func(idx int) HeadProcessor { - return HeadProcessorFn(func(_ context.Context, head eth.L1BlockRef) error { - processed[idx] = head - return nil - }) - } - headUpdates := newHeadUpdateProcessor(logger, nil, []HeadProcessor{makeProcessor(0), makeProcessor(1), makeProcessor(2)}, nil) - block := eth.L1BlockRef{Number: 110, Hash: common.Hash{0xaa}} - headUpdates.OnNewSafeHead(context.Background(), block) - require.Equal(t, []eth.L1BlockRef{block, block, block}, processed) - }) - - t.Run("NotifyFinalizedHeadProcessors", func(t *testing.T) { - logger := testlog.Logger(t, log.LvlInfo) - processed := make([]eth.L1BlockRef, 3) - makeProcessor := func(idx int) HeadProcessor { - return HeadProcessorFn(func(_ context.Context, head eth.L1BlockRef) error { - processed[idx] = head - return nil - }) - } - headUpdates := newHeadUpdateProcessor(logger, nil, nil, []HeadProcessor{makeProcessor(0), makeProcessor(1), makeProcessor(2)}) - block := eth.L1BlockRef{Number: 110, Hash: common.Hash{0xaa}} - headUpdates.OnNewFinalizedHead(context.Background(), block) - require.Equal(t, []eth.L1BlockRef{block, block, block}, processed) - }) -} diff --git a/op-supervisor/supervisor/entrypoint.go b/op-supervisor/supervisor/entrypoint.go index 86befabb5da99..dc4a272488f63 100644 --- a/op-supervisor/supervisor/entrypoint.go +++ b/op-supervisor/supervisor/entrypoint.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/urfave/cli/v2" "github.com/ethereum/go-ethereum/log" @@ -12,6 +11,7 @@ import ( opservice "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/cliapp" oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/ethereum-optimism/optimism/op-supervisor/flags" ) diff --git a/op-supervisor/supervisor/frontend/frontend.go b/op-supervisor/supervisor/frontend/frontend.go index b77b6b3edeebe..6a43e1fedb0f0 100644 --- a/op-supervisor/supervisor/frontend/frontend.go +++ b/op-supervisor/supervisor/frontend/frontend.go @@ -3,11 +3,9 @@ package frontend import ( "context" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" ) type AdminBackend interface { @@ -19,32 +17,37 @@ type AdminBackend interface { type QueryBackend interface { CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) CheckMessages(messages []types.Message, minSafety types.SafetyLevel) error - CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) - DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.BlockRef, error) + CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) + UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) + SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) + Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) } type UpdatesBackend interface { - UpdateLocalUnsafe(chainID types.ChainID, head eth.BlockRef) - UpdateLocalSafe(chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) - UpdateFinalizedL1(chainID types.ChainID, finalized eth.BlockRef) + UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error + UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) error + UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.BlockRef) error } type Backend interface { AdminBackend QueryBackend + UpdatesBackend } type QueryFrontend struct { Supervisor QueryBackend } +var _ QueryBackend = (*QueryFrontend)(nil) + // CheckMessage checks the safety-level of an individual message. // The payloadHash references the hash of the message-payload of the message. func (q *QueryFrontend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { return q.Supervisor.CheckMessage(identifier, payloadHash) } -// CheckMessage checks the safety-level of a collection of messages, +// CheckMessages checks the safety-level of a collection of messages, // and returns if the minimum safety-level is met for all messages. func (q *QueryFrontend) CheckMessages( messages []types.Message, @@ -53,29 +56,27 @@ func (q *QueryFrontend) CheckMessages( } func (q *QueryFrontend) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) { - // TODO(#12358): attach to backend - return types.ReferenceView{}, nil + return q.Supervisor.UnsafeView(ctx, chainID, unsafe) } func (q *QueryFrontend) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) { - // TODO(#12358): attach to backend - return types.ReferenceView{}, nil + return q.Supervisor.SafeView(ctx, chainID, safe) } func (q *QueryFrontend) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) { - // TODO(#12358): attach to backend - return eth.BlockID{}, nil + return q.Supervisor.Finalized(ctx, chainID) } -func (q *QueryFrontend) DerivedFrom(ctx context.Context, chainID types.ChainID, blockHash common.Hash, blockNumber uint64) (eth.BlockRef, error) { - // TODO(#12358): attach to backend - return eth.BlockRef{}, nil +func (q *QueryFrontend) CrossDerivedFrom(ctx context.Context, chainID types.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) { + return q.Supervisor.CrossDerivedFrom(ctx, chainID, derived) } type AdminFrontend struct { Supervisor Backend } +var _ AdminBackend = (*AdminFrontend)(nil) + // Start starts the service, if it was previously stopped. func (a *AdminFrontend) Start(ctx context.Context) error { return a.Supervisor.Start(ctx) @@ -95,14 +96,16 @@ type UpdatesFrontend struct { Supervisor UpdatesBackend } -func (u *UpdatesFrontend) UpdateLocalUnsafe(chainID types.ChainID, head eth.BlockRef) { - u.Supervisor.UpdateLocalUnsafe(chainID, head) +var _ UpdatesBackend = (*UpdatesFrontend)(nil) + +func (u *UpdatesFrontend) UpdateLocalUnsafe(ctx context.Context, chainID types.ChainID, head eth.BlockRef) error { + return u.Supervisor.UpdateLocalUnsafe(ctx, chainID, head) } -func (u *UpdatesFrontend) UpdateLocalSafe(chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) { - u.Supervisor.UpdateLocalSafe(chainID, derivedFrom, lastDerived) +func (u *UpdatesFrontend) UpdateLocalSafe(ctx context.Context, chainID types.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) error { + return u.Supervisor.UpdateLocalSafe(ctx, chainID, derivedFrom, lastDerived) } -func (u *UpdatesFrontend) UpdateFinalizedL1(chainID types.ChainID, finalized eth.BlockRef) { - u.Supervisor.UpdateFinalizedL1(chainID, finalized) +func (u *UpdatesFrontend) UpdateFinalizedL1(ctx context.Context, chainID types.ChainID, finalized eth.BlockRef) error { + return u.Supervisor.UpdateFinalizedL1(ctx, chainID, finalized) } diff --git a/op-supervisor/supervisor/service.go b/op-supervisor/supervisor/service.go index 47fcb3e9ec23e..7054c7e0ff555 100644 --- a/op-supervisor/supervisor/service.go +++ b/op-supervisor/supervisor/service.go @@ -4,10 +4,8 @@ import ( "context" "errors" "fmt" - "io" "sync/atomic" - "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" @@ -16,6 +14,7 @@ import ( opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/ethereum-optimism/optimism/op-supervisor/metrics" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" @@ -23,7 +22,6 @@ import ( type Backend interface { frontend.Backend - io.Closer } // SupervisorService implements the full-environment bells and whistles around the Supervisor. @@ -74,6 +72,17 @@ func (su *SupervisorService) initBackend(ctx context.Context, cfg *config.Config su.backend = backend.NewMockBackend() return nil } + // the flag is a string slice, which has the potential to have empty strings + filterBlank := func(in []string) []string { + out := make([]string, 0, len(in)) + for _, s := range in { + if s != "" { + out = append(out, s) + } + } + return out + } + cfg.L2RPCs = filterBlank(cfg.L2RPCs) be, err := backend.NewSupervisorBackend(ctx, su.log, su.metrics, cfg) if err != nil { return fmt.Errorf("failed to create supervisor backend: %w", err) @@ -149,6 +158,11 @@ func (su *SupervisorService) initRPCServer(cfg *config.Config) error { Service: &frontend.QueryFrontend{Supervisor: su.backend}, Authenticated: false, }) + server.AddAPI(rpc.API{ + Namespace: "supervisor", + Service: &frontend.UpdatesFrontend{Supervisor: su.backend}, + Authenticated: false, + }) su.rpcServer = server return nil } @@ -170,6 +184,7 @@ func (su *SupervisorService) Start(ctx context.Context) error { func (su *SupervisorService) Stop(ctx context.Context) error { if !su.closing.CompareAndSwap(false, true) { + su.log.Warn("Supervisor is already closing") return nil // already closing } su.log.Info("Stopping JSON-RPC server") @@ -179,16 +194,19 @@ func (su *SupervisorService) Stop(ctx context.Context) error { result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err)) } } + su.log.Info("Stopped RPC Server") if su.backend != nil { - if err := su.backend.Close(); err != nil { + if err := su.backend.Stop(ctx); err != nil { result = errors.Join(result, fmt.Errorf("failed to close supervisor backend: %w", err)) } } + su.log.Info("Stopped Backend") if su.pprofService != nil { if err := su.pprofService.Stop(ctx); err != nil { result = errors.Join(result, fmt.Errorf("failed to stop PProf server: %w", err)) } } + su.log.Info("Stopped PProf") if su.metricsSrv != nil { if err := su.metricsSrv.Stop(ctx); err != nil { result = errors.Join(result, fmt.Errorf("failed to stop metrics server: %w", err)) diff --git a/op-supervisor/supervisor/service_test.go b/op-supervisor/supervisor/service_test.go index a61b96937f6e6..9fce3ec48b4c8 100644 --- a/op-supervisor/supervisor/service_test.go +++ b/op-supervisor/supervisor/service_test.go @@ -17,10 +17,14 @@ import ( oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-supervisor/config" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) func TestSupervisorService(t *testing.T) { + depSet, err := depset.NewStaticConfigDependencySet(make(map[types.ChainID]*depset.StaticConfigDependency)) + require.NoError(t, err) + cfg := &config.Config{ Version: "", LogConfig: oplog.CLIConfig{ @@ -46,7 +50,8 @@ func TestSupervisorService(t *testing.T) { ListenPort: 0, // pick a port automatically EnableAdmin: true, }, - MockRun: true, + DependencySetSource: depSet, + MockRun: true, } logger := testlog.Logger(t, log.LevelError) supervisor, err := SupervisorFromConfig(context.Background(), cfg, logger) diff --git a/op-supervisor/supervisor/types/error.go b/op-supervisor/supervisor/types/error.go new file mode 100644 index 0000000000000..fc493fb87f913 --- /dev/null +++ b/op-supervisor/supervisor/types/error.go @@ -0,0 +1,30 @@ +package types + +import "errors" + +var ( + // ErrOutOfOrder happens when you try to add data to the DB, + // but it does not actually fit onto the latest data (by being too old or new). + ErrOutOfOrder = errors.New("data out of order") + // ErrDataCorruption happens when the underlying DB has some I/O issue + ErrDataCorruption = errors.New("data corruption") + // ErrSkipped happens when we try to retrieve data that is not available (pruned) + // It may also happen if we erroneously skip data, that was not considered a conflict, if the DB is corrupted. + ErrSkipped = errors.New("skipped data") + // ErrFuture happens when data is just not yet available + ErrFuture = errors.New("future data") + // ErrConflict happens when we know for sure that there is different canonical data + ErrConflict = errors.New("conflicting data") + // ErrStop can be used in iterators to indicate iteration has to stop + ErrStop = errors.New("iter stop") + // ErrOutOfScope is when data is accessed, but access is not allowed, because of a limited scope. + // E.g. when limiting scope to L2 blocks derived from a specific subset of the L1 chain. + ErrOutOfScope = errors.New("out of scope") + // ErrPreviousToFirst is when you try to get the previous block of the first block + // E.g. when calling PreviousDerivedFrom on the first L1 block in the DB. + ErrPreviousToFirst = errors.New("cannot get parent of first block in the database") + // ErrUnknownChain is when a chain is unknown, not in the dependency set. + ErrUnknownChain = errors.New("unknown chain") + // ErrNoRPCSource happens when a sub-service needs an RPC data source, but is not configured with one. + ErrNoRPCSource = errors.New("no RPC client configured") +) diff --git a/op-supervisor/supervisor/types/types.go b/op-supervisor/supervisor/types/types.go index 0224b9c29e93c..8f2b32bb227ac 100644 --- a/op-supervisor/supervisor/types/types.go +++ b/op-supervisor/supervisor/types/types.go @@ -6,23 +6,52 @@ import ( "fmt" "math" "math/big" + "strconv" + + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/holiman/uint256" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum-optimism/optimism/op-service/eth" ) +// ChainIndex represents the lifetime of a chain in a dependency set. +type ChainIndex uint32 + +func (ci ChainIndex) String() string { + return strconv.FormatUint(uint64(ci), 10) +} + +func (ci ChainIndex) MarshalText() ([]byte, error) { + return []byte(ci.String()), nil +} + +func (ci *ChainIndex) UnmarshalText(data []byte) error { + v, err := strconv.ParseUint(string(data), 10, 32) + if err != nil { + return err + } + *ci = ChainIndex(v) + return nil +} + type ExecutingMessage struct { - Chain uint32 // same as ChainID for now, but will be indirect, i.e. translated to full ID, later + Chain ChainIndex // same as ChainID for now, but will be indirect, i.e. translated to full ID, later BlockNum uint64 LogIdx uint32 Timestamp uint64 Hash common.Hash } +func (s *ExecutingMessage) String() string { + return fmt.Sprintf("ExecMsg(chainIndex: %s, block: %d, log: %d, time: %d, logHash: %s)", + s.Chain, s.BlockNum, s.LogIdx, s.Timestamp, s.Hash) +} + type Message struct { Identifier Identifier `json:"identifier"` PayloadHash common.Hash `json:"payloadHash"` @@ -31,7 +60,7 @@ type Message struct { type Identifier struct { Origin common.Address BlockNumber uint64 - LogIndex uint64 + LogIndex uint32 Timestamp uint64 ChainID ChainID // flat, not a pointer, to make Identifier safe as map key } @@ -61,7 +90,10 @@ func (id *Identifier) UnmarshalJSON(input []byte) error { } id.Origin = dec.Origin id.BlockNumber = uint64(dec.BlockNumber) - id.LogIndex = uint64(dec.LogIndex) + if dec.LogIndex > math.MaxUint32 { + return fmt.Errorf("log index too large: %d", dec.LogIndex) + } + id.LogIndex = uint32(dec.LogIndex) id.Timestamp = uint64(dec.Timestamp) id.ChainID = (ChainID)(dec.ChainID) return nil @@ -99,19 +131,27 @@ func (lvl *SafetyLevel) UnmarshalText(text []byte) error { } // AtLeastAsSafe returns true if the receiver is at least as safe as the other SafetyLevel. +// Safety levels are assumed to graduate from LocalUnsafe to LocalSafe to CrossUnsafe to CrossSafe, with Finalized as the strongest. func (lvl *SafetyLevel) AtLeastAsSafe(min SafetyLevel) bool { - switch min { - case Invalid: - return true - case CrossUnsafe: - return *lvl != Invalid - case CrossSafe: - return *lvl == CrossSafe || *lvl == Finalized - case Finalized: - return *lvl == Finalized - default: + relativeSafety := map[SafetyLevel]int{ + Invalid: 0, + LocalUnsafe: 1, + LocalSafe: 2, + CrossUnsafe: 3, + CrossSafe: 4, + Finalized: 5, + } + // if either level is not recognized, return false + _, ok := relativeSafety[*lvl] + if !ok { return false } + _, ok = relativeSafety[min] + if !ok { + return false + } + // compare the relative safety levels to determine if the receiver is at least as safe as the other + return relativeSafety[*lvl] >= relativeSafety[min] } const ( @@ -163,6 +203,28 @@ func (id ChainID) ToUInt32() (uint32, error) { return uint32(v64), nil } +func (id *ChainID) ToBig() *big.Int { + return (*uint256.Int)(id).ToBig() +} + +func (id ChainID) MarshalText() ([]byte, error) { + return []byte(id.String()), nil +} + +func (id *ChainID) UnmarshalText(data []byte) error { + var x uint256.Int + err := x.UnmarshalText(data) + if err != nil { + return err + } + *id = ChainID(x) + return nil +} + +func (id ChainID) Cmp(other ChainID) int { + return (*uint256.Int)(&id).Cmp((*uint256.Int)(&other)) +} + type ReferenceView struct { Local eth.BlockID `json:"local"` Cross eth.BlockID `json:"cross"` @@ -171,3 +233,80 @@ type ReferenceView struct { func (v ReferenceView) String() string { return fmt.Sprintf("View(local: %s, cross: %s)", v.Local, v.Cross) } + +type BlockSeal struct { + Hash common.Hash + Number uint64 + Timestamp uint64 +} + +func (s BlockSeal) String() string { + return fmt.Sprintf("BlockSeal(hash:%s, number:%d, time:%d)", s.Hash, s.Number, s.Timestamp) +} + +func (s BlockSeal) ID() eth.BlockID { + return eth.BlockID{Hash: s.Hash, Number: s.Number} +} + +func (s BlockSeal) MustWithParent(parent eth.BlockID) eth.BlockRef { + ref, err := s.WithParent(parent) + if err != nil { + panic(err) + } + return ref +} + +func (s BlockSeal) WithParent(parent eth.BlockID) (eth.BlockRef, error) { + // prevent parent attachment if the parent is not the previous block, + // and the block is not the genesis block + if s.Number != parent.Number+1 && s.Number != 0 { + return eth.BlockRef{}, fmt.Errorf("invalid parent block %s to combine with %s", parent, s) + } + return eth.BlockRef{ + Hash: s.Hash, + Number: s.Number, + ParentHash: parent.Hash, + Time: s.Timestamp, + }, nil +} + +func (s BlockSeal) ForceWithParent(parent eth.BlockID) eth.BlockRef { + return eth.BlockRef{ + Hash: s.Hash, + Number: s.Number, + ParentHash: parent.Hash, + Time: s.Timestamp, + } +} + +func BlockSealFromRef(ref eth.BlockRef) BlockSeal { + return BlockSeal{ + Hash: ref.Hash, + Number: ref.Number, + Timestamp: ref.Time, + } +} + +// PayloadHashToLogHash converts the payload hash to the log hash +// it is the concatenation of the log's address and the hash of the log's payload, +// which is then hashed again. This is the hash that is stored in the log storage. +// The logHash can then be used to traverse from the executing message +// to the log the referenced initiating message. +func PayloadHashToLogHash(payloadHash common.Hash, addr common.Address) common.Hash { + msg := make([]byte, 0, 2*common.HashLength) + msg = append(msg, addr.Bytes()...) + msg = append(msg, payloadHash.Bytes()...) + return crypto.Keccak256Hash(msg) +} + +// LogToMessagePayload is the data that is hashed to get the payloadHash +// it is the concatenation of the log's topics and data +// the implementation is based on the interop messaging spec +func LogToMessagePayload(l *ethTypes.Log) []byte { + msg := make([]byte, 0) + for _, topic := range l.Topics { + msg = append(msg, topic.Bytes()...) + } + msg = append(msg, l.Data...) + return msg +} diff --git a/op-supervisor/supervisor/types/types_test.go b/op-supervisor/supervisor/types/types_test.go index 50c856bdc7945..7e3e0d4843a91 100644 --- a/op-supervisor/supervisor/types/types_test.go +++ b/op-supervisor/supervisor/types/types_test.go @@ -13,7 +13,7 @@ import ( ) func FuzzRoundtripIdentifierJSONMarshal(f *testing.F) { - f.Fuzz(func(t *testing.T, origin []byte, blockNumber uint64, logIndex uint64, timestamp uint64, chainID []byte) { + f.Fuzz(func(t *testing.T, origin []byte, blockNumber uint64, logIndex uint32, timestamp uint64, chainID []byte) { if len(chainID) > 32 { chainID = chainID[:32] } @@ -55,7 +55,19 @@ func TestChainID_String(t *testing.T) { for _, test := range tests { test := test t.Run(test.expected, func(t *testing.T) { - require.Equal(t, test.expected, test.input.String()) + t.Run("String", func(t *testing.T) { + require.Equal(t, test.expected, test.input.String()) + }) + t.Run("MarshalText", func(t *testing.T) { + data, err := test.input.MarshalText() + require.NoError(t, err) + require.Equal(t, test.expected, string(data)) + }) + t.Run("UnmarshalText", func(t *testing.T) { + var id ChainID + require.NoError(t, id.UnmarshalText([]byte(test.expected))) + require.Equal(t, test.input, id) + }) }) } } diff --git a/op-ufm/README.md b/op-ufm/README.md deleted file mode 100644 index 08d1b4a6eca70..0000000000000 --- a/op-ufm/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# ⚠️ Important -This project has been moved to [ethereum-optimism/infra](https://github.com/ethereum-optimism/infra) - -# OP User Facing Monitoring - -This project simulates a synthetic user interacting with a OP Stack chain. - -It is intended to be used as a tool for monitoring -the health of the network by measuring end-to-end transaction latency. - - -## Metrics - -* Round-trip duration time to get transaction receipt (from creation timestamp) - -* First-seen duration time (from creation timestamp) - - -## Usage - -Run `make ufm` to build the binary. No additional dependencies are necessary. - -Copy `example.config.toml` to `config.toml` and edit the file to configure the service. - -Start the service with `ufm config.toml`. - diff --git a/op-wheel/Makefile b/op-wheel/Makefile index 06d114012022e..78363075e9951 100644 --- a/op-wheel/Makefile +++ b/op-wheel/Makefile @@ -1,14 +1,3 @@ -GITCOMMIT ?= $(shell git rev-parse HEAD) -GITDATE ?= $(shell git show -s --format='%ct') -VERSION ?= v0.0.0 +DEPRECATED_TARGETS := op-wheel -LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) -LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) -LDFLAGSSTRING +=-X main.Version=$(VERSION) -LDFLAGS := -ldflags "$(LDFLAGSSTRING)" - -op-wheel: - env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/op-wheel ./cmd - -.PHONY: \ - op-wheel +include ../just/deprecated.mk diff --git a/op-wheel/README.md b/op-wheel/README.md new file mode 100644 index 0000000000000..ce5dfa298f147 --- /dev/null +++ b/op-wheel/README.md @@ -0,0 +1,92 @@ +# `op-wheel` + +Issues: [monorepo](https://github.com/ethereum-optimism/optimism/issues?q=is%3Aissue%20state%3Aopen%20label%3AA-op-wheel) + +Pull requests: [monorepo](https://github.com/ethereum-optimism/optimism/pulls?q=is%3Aopen+is%3Apr+label%3AA-op-wheel) + +`op-wheel` is a CLI tool to direct the engine one way or the other with DB cheats and Engine API routines. + +It was named the "wheel" because of two reasons: +- Figuratively, it allows to steer the stack, an interface for a *driver* (like the op-node sub-component) to control the execution *engine* (e.g. op-geth). +- Idiomatically, like the Unix wheel-bit and its slang origins: empower a user to execute restricted commands, or more generally just someone with great power or influence. + +## Quickstart + +### Cheat utils + +Cheating commands to modify a Geth database without corresponding in-protocol change. + +The `cheat` sub-command has sub-commands for interacting with the DB, making patches, and dumping debug data. + +Note that the validity of state-changes, as applied through patches, +does not get checked until the block is re-processed. +This can be used ot trick the node into things like hypothetical +test-states or shadow-forks without diverging the block-hashes. + +To run: +```bash +go run ./op-wheel/cmd cheat --help +``` + +### Engine utils + +Engine API commands to build/reorg/rewind/finalize/copy blocks. + +Each sub-command dials the engine API endpoint (with provided JWT secret) and then runs the action. + +To run: +```bash +go run ./op-wheel/cmd engine --help +``` + +## Usage + +### Build from source + +```bash +# from op-wheel dir: +make op-wheel +./bin/op-wheel --help +``` + +### Run from source + +```bash +# from op-wheel dir: +go run ./cmd --help +``` + +### Build docker image + +See `op-wheel` docker-bake target. + +## Product + +`op-wheel` is a tool for expert-users to perform advanced data recoveries, tests and overrides. +This tool optimizes for reusability of these expert actions, to make them less error-prone. + +This is not part of a standard release / process, as this tool is not used commonly, +and the end-user is expected to be familiar with building from source. + +Actions that are common enough to be used at least once by the average end-user should +be part of the op-node or other standard op-stack release. + +## Design principles + +Design for an expert-user: this tool aims to provide full control over critical op-stack data +such as the engine-API and database itself, without hiding important information. + +However, even as expert-user, wrong assumptions can be made. +Defaults should aim to reduce errors, and leave the stack in a safe state to recover from. + +## Failure modes + +This tool is not used in the happy-path, but can be critical during expert-recovery of advanced failure modes. +E.g. database recovery after Geth database corruption, or manual forkchoice overrides. +Most importantly, each CLI command used for recovery aims to be verbose, +and avoids leaving an inconsistent state after failed or interrupted recovery. + +## Testing + +This is a test-utility more than a production tool, and thus does currently not have test-coverage of its own. +However, when it is used as tool during (dev/test) chain or node issues, usage does inform fixes/improvements. diff --git a/op-wheel/cheat/cheat.go b/op-wheel/cheat/cheat.go index 4b2d428e1029b..1b5089c283648 100644 --- a/op-wheel/cheat/cheat.go +++ b/op-wheel/cheat/cheat.go @@ -138,7 +138,7 @@ func (ch *Cheater) RunAndClose(fn HeadFn) error { // Geth stores the TD for each block separately from the block itself. We must update this // manually, otherwise Geth thinks we haven't reached TTD yet and tries to build a block - // using Clique consensus, which causes a panic. + // using pre-merge consensus, which causes a panic. rawdb.WriteTd(batch, blockHash, preID.Number, ch.Blockchain.GetTd(preID.Hash, preID.Number)) // Need to copy over receipts since they are keyed by block hash. diff --git a/op-wheel/justfile b/op-wheel/justfile new file mode 100644 index 0000000000000..40696592ed3b0 --- /dev/null +++ b/op-wheel/justfile @@ -0,0 +1,13 @@ +import '../just/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X main.Version=" + VERSION + " " + \ + "") + "'" + +BINARY := "./bin/op-wheel" + +# Build op-wheel binary +op-wheel: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) \ No newline at end of file diff --git a/ops-bedrock/docker-compose.yml b/ops-bedrock/docker-compose.yml index f1f9704070320..ee7f3d4269312 100644 --- a/ops-bedrock/docker-compose.yml +++ b/ops-bedrock/docker-compose.yml @@ -11,6 +11,7 @@ volumes: challenger_data: da_data: op_log: + dac_data: services: @@ -88,12 +89,30 @@ services: environment: GETH_MINER_RECOMMIT: 100ms + dac-server: + depends_on: + - l1 + - l1-bn + - l1-vc + build: + context: ../ + dockerfile: ops/docker/op-stack-go/Dockerfile + target: op-node-target + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:devnet + ports: + - 8888:8888 + command: > + da-server da start --config /usr/local/bin/default.json + volumes: + - "dac_data:/root/da/data" + op-node: depends_on: - l1 - l1-bn - l1-vc - l2 + - dac-server build: context: ../ dockerfile: ops/docker/op-stack-go/Dockerfile @@ -129,6 +148,7 @@ services: --altda.enabled=${ALTDA_ENABLED} --altda.da-service=${ALTDA_SERVICE} --altda.da-server=http://da-server:3100 + --dac.urls=http://dac-server:8888 ports: - "7545:8545" - "9003:9003" diff --git a/ops-bedrock/l1-geth.Dockerfile b/ops-bedrock/l1-geth.Dockerfile index c84a5debf72ee..50262ec94ea6e 100644 --- a/ops-bedrock/l1-geth.Dockerfile +++ b/ops-bedrock/l1-geth.Dockerfile @@ -1,4 +1,4 @@ -FROM ethereum/client-go:v1.14.11 +FROM ethereum/client-go:v1.14.12 RUN apk add --no-cache jq bash diff --git a/ops-bedrock/l2-op-geth-entrypoint.sh b/ops-bedrock/l2-op-geth-entrypoint.sh index f760a87f18750..eee8684e97b03 100644 --- a/ops-bedrock/l2-op-geth-entrypoint.sh +++ b/ops-bedrock/l2-op-geth-entrypoint.sh @@ -31,12 +31,12 @@ exec geth \ --http.vhosts="*" \ --http.addr=0.0.0.0 \ --http.port="$RPC_PORT" \ - --http.api=web3,debug,eth,txpool,net,engine \ + --http.api=web3,debug,eth,txpool,net,engine,miner \ --ws \ --ws.addr=0.0.0.0 \ --ws.port="$WS_PORT" \ --ws.origins="*" \ - --ws.api=debug,eth,txpool,net,engine \ + --ws.api=debug,eth,txpool,net,engine,miner \ --syncmode=full \ --nodiscover \ --maxpeers=0 \ diff --git a/ops-bedrock/l2-op-geth-interop.Dockerfile b/ops-bedrock/l2-op-geth-interop.Dockerfile new file mode 100644 index 0000000000000..5021fede46db9 --- /dev/null +++ b/ops-bedrock/l2-op-geth-interop.Dockerfile @@ -0,0 +1,10 @@ +FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101411.2-rc.1 +# Note: depend on dev-release for sequencer interop message checks + +RUN apk add --no-cache jq + +COPY l2-op-geth-entrypoint.sh /entrypoint.sh + +VOLUME ["/db"] + +ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] diff --git a/ops/docker/Dockerfile.packages b/ops/docker/Dockerfile.packages index fd2939cb45ffc..d4c8041321b15 100644 --- a/ops/docker/Dockerfile.packages +++ b/ops/docker/Dockerfile.packages @@ -24,7 +24,7 @@ COPY --from=foundry /usr/local/bin/cast /usr/local/bin/cast WORKDIR /opt/optimism -COPY ./versions.json ./versions.json +COPY ./mise.toml ./mise.toml COPY ./packages ./packages COPY .git/ ./.git COPY .gitmodules ./.gitmodules @@ -33,41 +33,3 @@ RUN git submodule update --init --recursive \ && cd packages/contracts-bedrock \ && just forge-build \ && echo $(git rev-parse HEAD) > .gitcommit - -FROM --platform=linux/amd64 debian:bookworm-20240812-slim as contracts-bedrock - -RUN apt-get update && apt-get install -y \ - curl \ - jq \ - ca-certificates \ - git \ - make \ - bash \ - --no-install-recommends - -COPY /ops/docker/oplabs.crt /usr/local/share/ca-certificates/oplabs.crt - -RUN chmod 644 /usr/local/share/ca-certificates/oplabs.crt \ - && update-ca-certificates - -COPY --from=foundry /usr/local/bin/just /usr/local/bin/just -COPY --from=foundry /usr/local/bin/forge /usr/local/bin/forge -COPY --from=foundry /usr/local/bin/cast /usr/local/bin/cast -COPY --from=foundry /usr/local/bin/svm /usr/local/bin/svm - -RUN svm install 0.8.25 && \ - svm install 0.8.15 && \ - svm install 0.8.19 && \ - svm install 0.8.26 - -# Not to be confused with OP, this is a OnePassword CLI tool. -COPY --from=1password/op:2 /usr/local/bin/op /usr/local/bin/op - -RUN mkdir -p /opt/optimism/packages/contracts-bedrock - -COPY --from=base /opt/optimism/packages/contracts-bedrock /opt/optimism/packages/contracts-bedrock -COPY --from=base /opt/optimism/versions.json /opt/optimism/versions.json - -WORKDIR /opt/optimism/packages/contracts-bedrock - -CMD ["echo", "Override this command to use this image."] diff --git a/ops/docker/ci-builder/Dockerfile b/ops/docker/ci-builder/Dockerfile index a1eb71b4795e1..cda9a0e71a9a0 100644 --- a/ops/docker/ci-builder/Dockerfile +++ b/ops/docker/ci-builder/Dockerfile @@ -1,120 +1,64 @@ -# Copy docker buildx in order to generate the absolute prestate -# in the CI pipeline for reproducible fault proof builds -FROM --platform=linux/amd64 docker as buildx +############################################################################### +# BUILDX # +############################################################################### + +FROM --platform=linux/amd64 docker AS buildx COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx RUN docker buildx version -FROM --platform=linux/amd64 debian:bullseye-slim as rust-build - -SHELL ["/bin/bash", "-c"] - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && \ - apt-get install -y build-essential git clang lld curl jq - -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \ - chmod +x ./rustup.sh && \ - sh rustup.sh -y - -# Install nightly toolchain -RUN source $HOME/.profile && rustup update nightly - -RUN source $HOME/.profile && cargo install just -RUN source $HOME/.profile && cargo install svm-rs -# Only diff from upstream docker image is this clone instead -# of COPY. We select a specific commit to use. -COPY ./versions.json ./versions.json -COPY ./ops/scripts/install-foundry.sh ./install-foundry.sh +############################################################################### +# CI BUILDER (BASE) # +############################################################################### -RUN curl -L https://foundry.paradigm.xyz | bash -RUN source $HOME/.profile && ./install-foundry.sh +FROM --platform=linux/amd64 debian:bullseye-slim AS base-builder -RUN strip /root/.foundry/bin/forge && \ - strip /root/.foundry/bin/cast && \ - strip /root/.foundry/bin/anvil && \ - strip /root/.cargo/bin/svm && \ - strip /root/.cargo/bin/just - -FROM --platform=linux/amd64 debian:bullseye-slim as go-build - -RUN apt-get update && apt-get install -y curl ca-certificates jq binutils - -ENV GO_VERSION=1.22.7 - -# Fetch go manually, rather than using a Go base image, so we can copy the installation into the final stage -RUN curl -sL https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -o go$GO_VERSION.linux-amd64.tar.gz && \ - tar -C /usr/local/ -xzvf go$GO_VERSION.linux-amd64.tar.gz - -ENV GOPATH=/go -ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH - -# Install the specific version of abigen and geth from version control -COPY ./versions.json ./versions.json -RUN go install github.com/ethereum/go-ethereum/cmd/abigen@$(jq -r .abigen < versions.json) -RUN go install github.com/ethereum/go-ethereum/cmd/geth@$(jq -r .geth < versions.json) - -RUN go install gotest.tools/gotestsum@v1.12.0 -RUN go install github.com/vektra/mockery/v2@v2.46.0 -RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 -RUN go install github.com/mikefarah/yq/v4@v4.44.3 +# Use bash as the shell +SHELL ["/bin/bash", "-c"] +ENV SHELL=/bin/bash +ENV BASH=/bin/bash -# Strip binaries to reduce size -RUN strip /go/bin/gotestsum && \ - strip /go/bin/mockery && \ - strip /go/bin/golangci-lint && \ - strip /go/bin/abigen && \ - strip /go/bin/geth && \ - strip /go/bin/yq +# Copy mise configuration +COPY ./mise.toml ./mise.toml -FROM --platform=linux/amd64 debian:bullseye-slim as base-builder +# Set up mise environment +ENV PATH="/root/.local/share/mise/shims:$PATH" +ENV PATH="/root/.local/bin:${PATH}" -ENV GOPATH=/go -ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH -ENV PATH=/root/.cargo/bin:$PATH +# Install dependencies +# We do this in one mega RUN command to avoid blowing up the size of the image ENV DEBIAN_FRONTEND=noninteractive - -# copy the go installation, but not the module cache (cache will get stale, and would add a lot of weight) -COPY --from=go-build /usr/local/go /usr/local/go - -# copy tools -COPY --from=go-build /go/bin/gotestsum /go/bin/gotestsum -COPY --from=go-build /go/bin/mockery /go/bin/mockery -COPY --from=go-build /go/bin/golangci-lint /go/bin/golangci-lint -COPY --from=go-build /go/bin/abigen /usr/local/bin/abigen -COPY --from=go-build /go/bin/geth /usr/local/bin/geth -COPY --from=go-build /go/bin/yq /go/bin/yq - -# copy tools -COPY --from=rust-build /root/.foundry/bin/forge /usr/local/bin/forge -COPY --from=rust-build /root/.foundry/bin/cast /usr/local/bin/cast -COPY --from=rust-build /root/.foundry/bin/anvil /usr/local/bin/anvil -COPY --from=rust-build /root/.cargo/bin/svm /usr/local/bin/svm -COPY --from=rust-build /root/.cargo/bin/just /usr/local/bin/just - -COPY ./versions.json ./versions.json - RUN /bin/sh -c set -eux; \ apt-get update; \ - apt-get install -y --no-install-recommends bash curl openssh-client git build-essential ca-certificates jq gnupg binutils-mips-linux-gnu python3 python3-pip python3-setuptools; \ + apt-get install -y --no-install-recommends bash curl openssh-client git build-essential ca-certificates gnupg binutils-mips-linux-gnu clang libffi-dev; \ mkdir -p /etc/apt/keyrings; \ curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg; \ chmod a+r /etc/apt/keyrings/docker.gpg; \ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null; \ apt-get update; \ apt-get install -y docker-ce-cli; \ - ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt; \ - pip install capstone pyelftools; \ - pip install semgrep==$(jq -r .semgrep < versions.json); \ + curl https://mise.run | sh; \ + mise trust ./mise.toml; \ + mise install; \ curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bash; \ + pip install capstone pyelftools; \ + go env -w GOMODCACHE=/go/pkg/mod; \ + go env -w GOCACHE=/root/.cache/go-build; \ + ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt; \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + go clean -cache -modcache -testcache; \ rm -rf /var/lib/apt/lists/*; \ - rm -rf /root/.cache/pip; + rm -rf /root/.cache/pip; \ + rm -rf /root/.cache/uv; \ + rm -rf /root/.rustup; -RUN svm install 0.8.25 && \ - svm install 0.8.15 && \ - svm install 0.8.19 +# Install Solidity versions +RUN echo "installing Solidity versions" && \ + svm install 0.8.25 && \ + svm install 0.8.19 && \ + svm install 0.8.15 +# Install Codecov uploader RUN echo "downloading and verifying Codecov uploader" && \ curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import && \ curl -Os "https://uploader.codecov.io/latest/linux/codecov" && \ @@ -129,24 +73,18 @@ RUN echo "downloading and verifying Codecov uploader" && \ # Copy docker buildx COPY --from=buildx /usr/libexec/docker/cli-plugins/docker-buildx /usr/libexec/docker/cli-plugins/docker-buildx -# within docker use bash -SHELL ["/bin/bash", "-c"] +# Set up entrypoint +ENTRYPOINT ["/bin/bash", "-c"] -# set env to use bash -ENV SHELL=/bin/bash -ENV BASH=/bin/bash -ENTRYPOINT ["/bin/bash", "-c"] +############################################################################### +# CI BUILDER (RUST) # +############################################################################### FROM base-builder as rust-builder # Install clang & lld RUN apt-get update && apt-get install -y clang lld -# Copy the rust installation, alongside the installed toolchains -COPY --from=rust-build /root/.cargo /root/.cargo -COPY --from=rust-build /root/.rustup /root/.rustup - -# copy the rust installation, alongside the installed toolchains -COPY --from=rust-build /root/.cargo/bin /root/.cargo/bin -COPY --from=rust-build /root/.rustup /root/.rustup +# Install nightly toolchain +RUN rustup update nightly diff --git a/ops/docker/ci-builder/Dockerfile.dockerignore b/ops/docker/ci-builder/Dockerfile.dockerignore index 229d6f1165c2b..4f44e253194ca 100644 --- a/ops/docker/ci-builder/Dockerfile.dockerignore +++ b/ops/docker/ci-builder/Dockerfile.dockerignore @@ -1,4 +1,3 @@ * !/.nvmrc -!/versions.json -!/ops/scripts/install-foundry.sh +!/mise.toml diff --git a/ops/docker/op-stack-go/Dockerfile b/ops/docker/op-stack-go/Dockerfile index 46105539c51a8..c4df9c776dca9 100644 --- a/ops/docker/op-stack-go/Dockerfile +++ b/ops/docker/op-stack-go/Dockerfile @@ -9,10 +9,26 @@ # It will default to the target platform. ARG TARGET_BASE_IMAGE=alpine:3.20 +# The ubuntu target base image is used for the op-challenger build with kona and asterisc. +ARG UBUNTU_TARGET_BASE_IMAGE=ubuntu:22.04 + +# The version of kona to use. +# The only build that uses this is `op-challenger-target`. +ARG KONA_VERSION=kona-client-v0.1.0-beta.5 + # We may be cross-building for another platform. Specify which platform we need as builder. FROM --platform=$BUILDPLATFORM golang:1.22.7-alpine3.20 AS builder -RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash +RUN apk add --no-cache curl tar gzip make gcc musl-dev linux-headers git jq bash + +# install yq +RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq && \ + chmod +x /usr/local/bin/yq + +# install versioned toolchain +COPY ./mise.toml . +RUN curl -L https://github.com/casey/just/releases/download/$(yq '.tools.just' mise.toml)/just-$(yq '.tools.just' mise.toml)-x86_64-unknown-linux-musl.tar.gz | \ + tar xz -C /usr/local/bin just # We copy the go.mod/sum first, so the `go mod download` does not have to re-run if dependencies do not change. COPY ./go.mod /app/go.mod @@ -47,7 +63,7 @@ ARG TARGETARCH # The "id" defaults to the value of "target", the cache will thus be reused during this build. # "sharing" defaults to "shared", the cache will thus be available to other concurrent docker builds. -FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.0.0-alpha.3 AS cannon-builder-0 +FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.0.0 AS cannon-builder-0 FROM --platform=$BUILDPLATFORM builder AS cannon-builder ARG CANNON_VERSION=v0.0.0 @@ -110,6 +126,10 @@ ARG OP_NODE_VERSION=v0.0.0 RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd op-chain-ops && make op-deployer \ GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_DEPLOYER_VERSION" +FROM --platform=$BUILDPLATFORM builder AS dac-server-builder +ARG OP_NODE_VERSION=v0.0.0 +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd da-server && go build -o da-server main.go + FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS cannon-target COPY --from=cannon-builder /app/cannon/bin/cannon /usr/local/bin/ COPY --from=cannon-builder /app/cannon/multicannon/embeds/* /usr/local/bin/ @@ -125,16 +145,27 @@ CMD ["op-wheel"] FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-node-target COPY --from=op-node-builder /app/op-node/bin/op-node /usr/local/bin/ +COPY --from=dac-server-builder /app/da-server/da-server /usr/local/bin/ +COPY --from=dac-server-builder /app/da-server/default.json /usr/local/bin/ CMD ["op-node"] -FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-challenger-target +# Make the kona docker image published by upstream available as a source to copy kona and asterisc from. +FROM --platform=$TARGETPLATFORM ghcr.io/op-rs/kona/kona-fpp-asterisc:$KONA_VERSION AS kona + +# Also produce an op-challenger loaded with kona and asterisc using ubuntu +FROM --platform=$TARGETPLATFORM $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/ -# Make the bundled op-program the default cannon server +# Copy in op-program and cannon COPY --from=op-program-builder /app/op-program/bin/op-program /usr/local/bin/ ENV OP_CHALLENGER_CANNON_SERVER /usr/local/bin/op-program -# Make the bundled cannon the default cannon executable 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 --from=kona /kona-host /usr/local/bin/ +ENV OP_CHALLENGER_ASTERISC_KONA_SERVER=/usr/local/bin/kona-host +COPY --from=kona /asterisc /usr/local/bin/ +ENV OP_CHALLENGER_ASTERISC_BIN=/usr/local/bin/asterisc CMD ["op-challenger"] FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-dispute-mon-target diff --git a/ops/docker/op-stack-go/Dockerfile.dockerignore b/ops/docker/op-stack-go/Dockerfile.dockerignore index 1c0841df1f334..27e79e6d06f43 100644 --- a/ops/docker/op-stack-go/Dockerfile.dockerignore +++ b/ops/docker/op-stack-go/Dockerfile.dockerignore @@ -4,8 +4,8 @@ !/cannon !/op-batcher -!/op-bootnode !/op-chain-ops +!/op-deployer !/op-challenger !/packages/contracts-bedrock/snapshots !/op-dispute-mon @@ -18,5 +18,8 @@ !/op-supervisor !/op-wheel !/op-alt-da +!/da-server !/go.mod !/go.sum +!/just +!/mise.toml diff --git a/ops/docker/proofs-tools/Dockerfile b/ops/docker/proofs-tools/Dockerfile index 61f39c8af7057..ad462156a8ef3 100644 --- a/ops/docker/proofs-tools/Dockerfile +++ b/ops/docker/proofs-tools/Dockerfile @@ -3,11 +3,9 @@ ARG GIT_DATE ARG CHALLENGER_VERSION ARG KONA_VERSION -ARG ASTERISC_VERSION FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:$CHALLENGER_VERSION AS challenger FROM --platform=$BUILDPLATFORM ghcr.io/anton-rs/kona/kona-fpp-asterisc:$KONA_VERSION AS kona -FROM --platform=$BUILDPLATFORM ghcr.io/ethereum-optimism/asterisc/asterisc:$ASTERISC_VERSION AS asterisc FROM --platform=$BUILDPLATFORM ubuntu:22.04 AS proofs-tools RUN apt-get update && apt-get install -y --no-install-recommends musl openssl ca-certificates @@ -19,8 +17,7 @@ ENV OP_CHALLENGER_CANNON_SERVER=/usr/local/bin/op-program COPY --from=kona /kona-host /usr/local/bin/ ENV OP_CHALLENGER_ASTERISC_KONA_SERVER=/usr/local/bin/kona-host - -COPY --from=asterisc /usr/local/bin/asterisc /usr/local/bin/ +COPY --from=kona /asterisc /usr/local/bin/ ENV OP_CHALLENGER_ASTERISC_BIN=/usr/local/bin/asterisc CMD /usr/local/bin/op-challenger diff --git a/ops/scripts/ci-docker-tag-op-stack-release.sh b/ops/scripts/ci-docker-tag-op-stack-release.sh index 45dd920949946..1de86b749d329 100755 --- a/ops/scripts/ci-docker-tag-op-stack-release.sh +++ b/ops/scripts/ci-docker-tag-op-stack-release.sh @@ -6,7 +6,7 @@ DOCKER_REPO=$1 GIT_TAG=$2 GIT_SHA=$3 -IMAGE_NAME=$(echo "$GIT_TAG" | grep -Eow '^(ci-builder(-rust)?|da-server|proofs-tools|cannon|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)' || true) +IMAGE_NAME=$(echo "$GIT_TAG" | grep -Eow '^(ci-builder(-rust)?|da-server|proofs-tools|holocene-deployer|cannon|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)' || true) if [ -z "$IMAGE_NAME" ]; then echo "image name could not be parsed from git tag '$GIT_TAG'" exit 1 diff --git a/ops/scripts/geth-version-checker.sh b/ops/scripts/geth-version-checker.sh deleted file mode 100755 index 98d94e6641365..0000000000000 --- a/ops/scripts/geth-version-checker.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -SCRIPTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -MONOREPO_DIR=$(cd "$SCRIPTS_DIR/../../" && pwd) - -# Extract the version from the geth command output -GETH_VERSION="v$(geth version | grep '^Version:' | awk '{print $2}')" - -# Read the version from the versions file -EXPECTED_GETH_VERSION=$(jq -r .geth < "$MONOREPO_DIR"/versions.json) - -# Check if EXPECTED_GETH_VERSION contains a '-'. If not, append '-stable'. -if [[ $EXPECTED_GETH_VERSION != *-* ]]; then - EXPECTED_GETH_VERSION="${EXPECTED_GETH_VERSION}-stable" -fi - -# Compare the versions -if [[ "$GETH_VERSION" == "$EXPECTED_GETH_VERSION" ]]; then - echo "Geth version $GETH_VERSION is correct!" - exit 0 -else - echo "Geth version does not match!" - echo "Local geth version: $GETH_VERSION" - echo "Expected geth version: $EXPECTED_GETH_VERSION" - exit 1 -fi - - diff --git a/ops/scripts/install-foundry.sh b/ops/scripts/install-foundry.sh deleted file mode 100755 index 654ed6b87f764..0000000000000 --- a/ops/scripts/install-foundry.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -set -e - -SCRIPTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -MONOREPO_DIR=$(cd "$SCRIPTS_DIR/../../" && pwd) - -# Grab the foundry commit hash. -SHA=$(jq -r .foundry < "$MONOREPO_DIR"/versions.json) - -# Check if there is a nightly tag corresponding to the commit hash -TAG="nightly-$SHA" - -# If the foundry repository exists and a branch is checked out, we need to abort -# any changes inside ~/.foundry/foundry-rs/foundry. This is because foundryup will -# attempt to pull the latest changes from the remote repository, which will fail -# if there are any uncommitted changes. -if [ -d ~/.foundry/foundry-rs/foundry ]; then - echo "Foundry repository exists! Aborting any changes..." - cd ~/.foundry/foundry-rs/foundry - git reset --hard - git clean -fd - cd - -fi - -# Create a temporary directory -TMP_DIR=$(mktemp -d) -echo "Created tempdir @ $TMP_DIR" - -# Clone the foundry repo temporarily. We do this to avoid the need for a personal access -# token to interact with the GitHub REST API, and clean it up after we're done. -git clone https://github.com/foundry-rs/foundry.git "$TMP_DIR" && cd "$TMP_DIR" - -# If the nightly tag exists, we can download the pre-built binaries rather than building -# from source. Otherwise, clone the repository, check out the commit SHA, and build `forge`, -# `cast`, `anvil`, and `chisel` from source. -if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Nightly tag exists! Downloading prebuilt binaries..." - foundryup -v "$TAG" -else - echo "Nightly tag doesn't exist! Building from source..." - git checkout "$SHA" - - # Use native `cargo` build to avoid any rustc environment variables `foundryup` sets. We explicitly - # ignore chisel, as it is not a part of `ci-builder`. - cargo build --bin forge --release - cargo build --bin cast --release - cargo build --bin anvil --release - mkdir -p ~/.foundry/bin - mv target/release/forge ~/.foundry/bin - mv target/release/cast ~/.foundry/bin - mv target/release/anvil ~/.foundry/bin -fi - -# Remove the temporary foundry repo; Used just for checking the nightly tag's existence. -rm -rf "$TMP_DIR" -echo "Removed tempdir @ $TMP_DIR" diff --git a/packages/contracts-bedrock/.gitignore b/packages/contracts-bedrock/.gitignore index 96e09c8c71901..ae6569a94ebea 100644 --- a/packages/contracts-bedrock/.gitignore +++ b/packages/contracts-bedrock/.gitignore @@ -3,7 +3,6 @@ artifacts forge-artifacts cache broadcast -kout-deployment kout-proofs test/kontrol/logs @@ -17,6 +16,7 @@ kontrol_prove_report.xml # Scripts scripts/go-ffi/go-ffi +scripts/go-ffi/go-ffi-cannon64 # Environment Variables .envrc diff --git a/packages/contracts-bedrock/CHANGELOG.md b/packages/contracts-bedrock/CHANGELOG.md deleted file mode 100644 index e4d02f372ade0..0000000000000 --- a/packages/contracts-bedrock/CHANGELOG.md +++ /dev/null @@ -1,491 +0,0 @@ -# @eth-optimism/contracts-bedrock - -## 0.17.3 - -### Patch Changes - -- [#10621](https://github.com/ethereum-optimism/optimism/pull/10621) [`eb454ac72b26211eb8037e7e777315c8f30d994d`](https://github.com/ethereum-optimism/optimism/commit/eb454ac72b26211eb8037e7e777315c8f30d994d) Thanks [@tarunkhasnavis](https://github.com/tarunkhasnavis)! - Add data field to faucet contract drip parameters - -## 0.17.2 - -### Patch Changes - -- [#9964](https://github.com/ethereum-optimism/optimism/pull/9964) [`8241220898128e1f61064f22dcb6fdd0a5f043c3`](https://github.com/ethereum-optimism/optimism/commit/8241220898128e1f61064f22dcb6fdd0a5f043c3) Thanks [@roninjin10](https://github.com/roninjin10)! - Removed only-allow command from package.json - -## 0.17.1 - -### Patch Changes - -- [#9415](https://github.com/ethereum-optimism/optimism/pull/9415) [`79effc52e8b82d15b5eda43acf540ac6c5f8d5d7`](https://github.com/ethereum-optimism/optimism/commit/79effc52e8b82d15b5eda43acf540ac6c5f8d5d7) Thanks [@nitaliano](https://github.com/nitaliano)! - Bumps version so fpac contracts exist for SDK to consume - -## 0.16.2 - -### Patch Changes - -- [#7450](https://github.com/ethereum-optimism/optimism/pull/7450) [`ac90e16a7`](https://github.com/ethereum-optimism/optimism/commit/ac90e16a7f85c4f73661ae6023135c3d00421c1e) Thanks [@roninjin10](https://github.com/roninjin10)! - Updated dev dependencies related to testing that is causing audit tooling to report failures - -## 0.16.1 - -### Patch Changes - -- [#7244](https://github.com/ethereum-optimism/optimism/pull/7244) [`2440f5e7a`](https://github.com/ethereum-optimism/optimism/commit/2440f5e7ab6577f2d2e9c8b0c78c014290dde8e7) Thanks [@nitaliano](https://github.com/nitaliano)! - bumps sdk version to have access to sepolia deployments - -## 0.16.0 - -### Minor Changes - -- [#6206](https://github.com/ethereum-optimism/optimism/pull/6206) [`72d184854`](https://github.com/ethereum-optimism/optimism/commit/72d184854ebad8b2025641f126ed76573b1f0ac3) Thanks [@tynes](https://github.com/tynes)! - Migrate contracts periphery into bedrock - -### Patch Changes - -- [#6164](https://github.com/ethereum-optimism/optimism/pull/6164) [`c11039060`](https://github.com/ethereum-optimism/optimism/commit/c11039060bc037a88916c2cba602687b6d69ad1a) Thanks [@pengin7384](https://github.com/pengin7384)! - fix typo - -- [#6198](https://github.com/ethereum-optimism/optimism/pull/6198) [`77da6edc6`](https://github.com/ethereum-optimism/optimism/commit/77da6edc643e0b5e39f7b6bb41c3c7ead418a876) Thanks [@tremarkley](https://github.com/tremarkley)! - Delete dead typescript https://github.com/ethereum-optimism/optimism/pull/6148. - -## 0.15.0 - -### Minor Changes - -- af292562f: Fix issue with deposits running out of gas - -### Patch Changes - -- Updated dependencies [8d7dcc70c] -- Updated dependencies [d6388be4a] - - @eth-optimism/core-utils@0.12.1 - -## 0.14.0 - -### Minor Changes - -- 197884eae: Bump XDM semver after #5444 -- 6eb05430d: Increase precision in `SafeCall.hasMinGas` -- 5063a69fb: Update sdk contract addresses for bedrock - -### Patch Changes - -- f1e867177: contracts-bedrock was exporting hardhat when it didn't need to be - -## 0.13.2 - -### Patch Changes - -- b16067a9f: Reduce the time that the system dictator deploy scripts wait before checking the chain state. -- 9a02079eb: Makes the Proxy contract inheritable by making functions (public virtual). -- 98fbe9d22: Added a contsructor to the System Dictator - -## 0.13.1 - -### Patch Changes - -- 22c3885f5: Optionally print cast commands during migration -- f52c07529: Print tenderly simulation links during deployment - -## 0.13.0 - -### Minor Changes - -- cb19e2f9c: Moves `FINALIZATION_PERIOD_SECONDS` from the `OptimismPortal` to the `L2OutputOracle` & ensures the `CHALLENGER` key cannot delete finalized outputs. - -## 0.12.1 - -### Patch Changes - -- 80f2271f5: Update foundry - -## 0.12.0 - -### Minor Changes - -- efc98d261: Change the `relayMessage` reentrancy guard in the XDMs to be per-message. - -### Patch Changes - -- 7c0a2cc37: add is IERC165 to IOptimismMintableERC20 -- 2865dd9b4: Minor comment updates and cleanup to the SystemConfig contract. -- 388f2c25a: Trigger a release including CrossDomainOwnable3 - -## 0.11.4 - -### Patch Changes - -- 3c22333b8: Loosens the requirements for re-proving a withdrawal transaction in the `OptimismPortal` - -## 0.11.3 - -### Patch Changes - -- 4964be480: Added a test for large deposit gaps - -## 0.11.2 - -### Patch Changes - -- 8784bc0bc: Add invariant test for the L1 XDM's `relayMessage` minimum gas limits. - -## 0.11.1 - -### Patch Changes - -- fe80a9488: Add echidna tests for portal -- 827fc7b04: Adds a go package to generate fuzz inputs for the Bedrock contract tests. -- a2166dcad: Add echidna tests for metering -- ff09ec22d: Add echidna tests for hashing -- 85dfa9fe2: Add echidna tests for encoding -- 0f8fc58ad: Add echidna tests for Burn -- 89f70c591: Add tests for the `Bytes` library -- 03940c3cb: Make lint check and fix scripts match - -## 0.11.0 - -### Minor Changes - -- 4d13f0afe: Refactors the MerkleTrie get function to throw explicitly instead of returning an existence boolean - -### Patch Changes - -- 43f33f39f: Add echidna test commands -- 237a351f1: Add tests to the SystemConfig contract -- 1d3c749a2: Bumps the version of ts-node used -- 1594678e0: Add echidna test for AliasHelper -- 1d3c749a2: Updates the version of TypeScript -- 136ea1785: Refactors the L2OutputOracle to key the l2Outputs mapping by index instead of by L2 block number. -- 7300a7ca7: Document test function naming convention and create a script for checking. -- Updated dependencies [c975c9620] -- Updated dependencies [136ea1785] - - @eth-optimism/core-utils@0.12.0 - -## 0.10.0 - -### Minor Changes - -- 59adcaa09: Deleted Unused Variables fundAccount , impersonatedTx -- 1bfe79f20: Adds an implementation of the Two Step Withdrawals V2 proposal - -### Patch Changes - -- c025a1153: Fixes a severe vulnerability found in ToB's November 2022 audit of the Bedrock contracts -- f8697a607: Removes historicalTotalBlocks from the L2OutputOracle -- c71500a7e: Updates L2OutputOracle to easily delete multiple outputs at once -- f49b71d50: Updated forge-std version -- ccaf5bc83: Allows owner and proposer addresses to be the same in L2OutputOracle - -## 0.9.1 - -### Patch Changes - -- 52079cc12: Has ProxyAdmin use Ownable instead of Owned -- 13bfafb21: Tweaks variable ordering in OptimismPortal -- eeae96941: Removes the unused DeployConfig contract -- 427831d86: Add comments to SystemConfig.sol - -## 0.9.0 - -### Minor Changes - -- 87702c741: Use SCREAMING_SNAKE_CASE for immutables - -### Patch Changes - -- c02831144: Introduces layout lock and more storage layout verification -- d58b0a397: Cleans up remaining lint warnings -- ff860ecf3: Introduces the MigrationSystemDictator for the Bedrock migration -- cc5adbc61: Updates function ordering in ProxyAdmin to match original contract -- 31c91ea74: Adds another assertion in the CrossDomainMessenger to explicitly identify an invariant which was previously implicit. -- Updated dependencies [1e76cdb86] - - @eth-optimism/core-utils@0.11.0 - -## 0.8.3 - -### Patch Changes - -- db84317b: Various RLP updates -- 9b90c732: Added codecov badge to readme - -## 0.8.2 - -### Patch Changes - -- 7d7d9ba8: Moves initializers underneath constructors always - -## 0.8.1 - -### Patch Changes - -- 35a7bb5e: Use uint64 for arithmetic in XDM's baseGas -- a5e715c3: Rename the event emitted in the L2ToL1MessagePasser -- d18b8aa3: Removes an unnecessary initializer parameter in the L200 - -## 0.8.0 - -### Minor Changes - -- 3d4e8529: No refunds! - -### Patch Changes - -- 6ed68fa3: Fixes a small bug in the constructor of the L2OutputOracle contract -- caf5dd3e: Updates README to include versioning rules. -- a6cbfee2: Fuzz L2ToL1MessagePasser -- 394a26ec: Modifies the StandardBridge to move a value check deeper down the call stack to be more defensive. - -## 0.7.0 - -### Minor Changes - -- e2faaa8b: Moves the L2ToL1MessagePasser to a new address and puts a LegacyMessagePasser at the old address. - -### Patch Changes - -- cb5fed67: Clarify intent with mintable token interfaces -- c427f0c0: Fixes to natspec docs -- d28ad592: Tweaks storage spacers to standardize names and use original types -- 76c8ee2d: Fix event names orderings for `OptimismMintableERC20Created` - -## 0.6.3 - -### Patch Changes - -- 88dde7c8: Uses assert rather than a require statements to check for conditions we believe are unreachable.This is more semantically explicit, and should enable us to more effectively use some advanced analysis methods in our testing. -- 7215f4ce: Bump ethers to 5.7.0 globally -- 249a8ed6: Fixed a backwards compatibility issue in which incorrect events were emitted during a failed deposit finalization on the L2 bridge. -- 7d7c4fdf: Makes spacers private and updates names to reflect slot, offset, and length. -- e164e22e: Makes finalizeWithdrawalTransaction not payable because it doesn't need to be and it was causing confusion throughout the codebase. -- 0bc1be45: Make the use of storage gaps consistent across contracts -- af3e56b1: Fix to Ensure the Oracle's owner and proposer accounts are unique -- 206f6033: Fix outdated references to 'withdrawal contract' -- 88dde7c8: Use assert statements for unreachable conditions. -- 8790156c: Simplifies the initialization function of the CrossDomainMessenger in Bedrock -- 515685f4: Update comments on GovernanceToken to match Seaport style. -- Updated dependencies [7215f4ce] -- Updated dependencies [206f6033] - - @eth-optimism/core-utils@0.10.1 - -## 0.6.2 - -### Patch Changes - -- 651a2883: Make spacer variables private in the Bridge and Messenger contracts so that they cannot be accessed in child contracts. - -## 0.6.1 - -### Patch Changes - -- 85232179: Add CrossDomainOwnable contracts -- 593f1cfb: Removes the blockedSystemMessages mapping in favor of a simpler approach to preventing messages from being sent to internal system addresses. -- f78eb056: Prevents v0 (legacy) messages from being relayed in the bedrock XDM. - -## 0.6.0 - -### Minor Changes - -- 7fdc490c: Removes initializer from StandardBridge in favor of immutables -- 3d228a0e: Updates the storage layout for the CrossDomainMessenger base contract to reduce diff with the existing system. - -### Patch Changes - -- 63ef1949: Delete hardhat genesis tasks -- Updated dependencies [dbfea116] - - @eth-optimism/core-utils@0.10.0 - -## 0.5.4 - -### Patch Changes - -- a095d544: Include latest devnet deployment artifacts -- cdf2163e: Bump oz packages to latest release -- 791f30bc: Migrate deploy config to json from ts -- 193befed: Fix nonce issue for parallel deployments -- 02420db0: Add missing predeploy to Predeploys.sol -- 94a8f287: Moves forge-std and ds-test to devDependencies to avoid breaking npm -- 7d03c5c0: Update the L2 genesis hardhat task to use the ProxyAdmin's deployed address as the admin of each predeploy -- fec22bfe: Fix legibility in the L2CrossDomainMessengerInitializer -- 9272253e: Make a library call internal -- c025f418: Add additional deployments of address manager and proxy admin -- 329d21b6: Use safecall that doesn't copy calldata -- 35eafed0: Deletes the L2 genesis creation hardhat task as its now replaced by go code -- 3cde9205: Update @foundry-rs/hardhat-forge to 0.1.17 - -## 0.5.3 - -### Patch Changes - -- 056cb982: Fix slither script -- a32e68ac: Update genesis-l2 task to set immutables in the bytecode -- c648d55c: Fix build for smaller package -- d544f804: Use the same initializable across codebase -- ccbfe545: Update @foundry-rs/hardhat-forge@0.1.16 -- c97ad241: Fix build on latest foundry -- 45541553: Emit an extra event when withdrawals are initiated to make chainops easier -- 3dd296e8: Fix portal deployment to have L2OutputOracle proxy address -- fe94b864: Add watch task -- 28649d64: Add harhdat forge contract verification support -- 898c7ac5: Update hardhat-forge dep, remove dead deps -- 51a1595b: bedrock-goerli-96f44f79 deployment -- 8ae39154: Update deposit transaction type -- af96563a: Fix typechain exports -- dac4a9f0: Updates the SDK to be compatible with Bedrock (via the "bedrock: true" constructor param). Updates the build pipeline for contracts-bedrock to export a properly formatted dist folder that matches our other packages. -- Updated dependencies [0df744f6] -- Updated dependencies [8ae39154] -- Updated dependencies [dac4a9f0] - - @eth-optimism/core-utils@0.9.3 - -## 0.5.2 - -### Patch Changes - -- 1a22e822: Standardizes revert strings globally -- 5e113137: Fixes a bug in the L2 Bedrock genesis script -- 177a9ea8: Cleans linting errors in MerkleTrie.sol -- 7d68f82f: Adds a new event SentMessageExtension1 to the CrossDomainMessenger contract. Includes additional data that's being attached to messages sent after the Bedrock upgrade. -- 90630336: Properly generates and exports ABI and artifact files that can be imported by client libraries -- 8bd7abde: Moves various legacy contracts into the legacy folder -- 7e6eb9b2: The output oracle's getL2Output function now reverts when no output is returned -- f243dacf: Bump to use solidity 0.8.15 -- 8d26459b: Remove subversion byte from deposit tx -- fa9823f3: Naming improvements for functions and variables in the L2OutputOracle -- 0bf3b9b4: Update forge-std -- e764cbb7: Shortens library names -- 3a0271f8: Introduces Types.sol -- 5de373ea: Semver contract updated to include a getter for the full version string -- Updated dependencies [0bf3b9b4] -- Updated dependencies [8d26459b] -- Updated dependencies [4477fe9f] - - @eth-optimism/core-utils@0.9.2 - -## 0.5.1 - -### Patch Changes - -- e4693481: Clean up BytesUtils -- b7b77d6c: Updates CrossDomainMessenger.baseGas to more accurately reflect gas costs -- 9d435aec: Cleans up natspec in MerkleTrie and SecureMerkleTrie contracts -- 87f745b5: Cleans up various compiler warnings -- 8a3074ab: Minor cleanups to initialization and semver for L1 contracts -- e1501bc0: Clears most contract linting warnings - -## 0.5.0 - -### Minor Changes - -- 42a4cc30: Remove Lib* and OVM* prefixes from all contracts - -### Patch Changes - -- 0cb3929e: Move encoding and hashing into Encoding and Hashing libraries -- 28bd76ae: Cleans up hashing and encoding library natspec and function names -- 4279647f: Port RLPWriter tests -- ce6cb121: Use external version of ExcessivelySafeCall -- 8986f165: Fix solc warnings in ProxyAdmin -- 69ee689f: Remove unnecessary DefaultValues library -- 2e89f634: Fixes a bug that caused L2 timestamps to be computed incorrectly -- 49d33b08: Standardizes comments, errors, and events for contracts in the /universal package -- 821907e2: Bump typechain to 8.1.0 -- 91b31168: Clean up comments and errors for legacy contracts -- 3c5726d4: Cleaned up enums, should be CapitalCase enums and UPPER_CASE values -- eb11a5bb: Add comments to RLP libraries -- 092b0901: Update to new L2 tx hash style for deposits -- 4ea33e13: Standardizes initialization logic for L1 contracts -- 297af083: Move contracts written by external parties into a vendor folder -- 71800503: Reduce the number of compiler warnings -- 611d93a1: Remove storage slot buffer in xdomain messengers -- 75089d0a: Cleans up initialization logic everywhere -- b9a90f32: Rename OptimismMintableTokenFactory to OptimismMintableERC20Factory -- 50e20ea1: Fix initialization logic -- 6f74ca9f: Clean up the PredeployAddresses library -- c031ec95: Tests for RLPReader -- 9c8b1f00: Bump forge-std to 62caef29b0f87a2c6aaaf634b2ca4c09b6867c92 -- 89d01f2e: Add semver to L2 contracts -- 7d9820b6: Resolve compiler warnings in Proxy.sol -- f9fee446: Move the `DepositTx` type to `core-utils`. This way it can be more easily used across projects -- 5050e0fb: Remove "not implemented" errors in virtual functions -- 78d7c2ec: Update typechain pipeline -- 89d01f2e: Update dev deps -- Updated dependencies [f9fee446] - - @eth-optimism/core-utils@0.9.1 - -## 0.4.1 - -### Patch Changes - -- 5c3b4bfa: Enable hardhat style buildinfo -- ef29d8a5: Make the Portal upgradeable -- 5bb6f2c7: Add `OptimismPortal.isOutputFinalized` -- 79f31007: correct l33t sp34k in toCodeAddrr -- 5a12c635: Add deployer docker image -- 8460865f: Optimize buildinfo support, only build through hardhat interface - -## 0.4.0 - -### Minor Changes - -- a828da9f: Add separate sequencer role to Oracle - -### Patch Changes - -- a828da9f: Separate the owner and sequencer roles in the OutputOracle -- 347fd37c: Fix bug in bedrock deploy scripts -- 700dcbb0: Add genesis script -- 931e517b: Fix order of args to L2OO constructor -- 93e2f750: Fix for incorrect constructor args in deploy config -- ddf515cb: Make the output oracle upgradeable. -- Updated dependencies [700dcbb0] - - @eth-optimism/core-utils@0.9.0 - -## 0.3.0 - -### Minor Changes - -- 35757456: Replaces L2 timestamps with block numbers as the key in mapping(uint => OutputProposal). - -### Patch Changes - -- f23bae0b: bedrock: ProxyAdmin rename OpenZeppelin proxy to ERC1967 -- fadb1a93: OZ Audit fixes with a Low or informational severity: - - - Hardcode constant values - - Require that msg.value == \_amount on ETH withdrawals - - use \_from in place of msg.sender when applicable in internal functions - -- f23bae0b: bedrock: Simplify ProxyAdmin static calls -- 650ca6d4: Fixes to medium severity OZ findings - - - Disallow reentrant withdrawals - - remove donateEth - - Correct ordering of \_from and \_to arguments on refunds of failed deposits - -- 9aa8049c: Have contracts-bedrock properly include contract sources in npm package - -## 0.2.0 - -### Minor Changes - -- 04884132: Corrects the ordering of token addresses when a finalizeBridgeERC20 call fails - -### Patch Changes - -- 0a5ca8bf: Deployment for bedrock contracts on goerli -- 2f3fae0e: Fix hh artifact schema -- a96cbe7c: Fix style for L2 contracts to match L1 contracts -- 29ff7462: Revert es target back to 2017 -- 14dd80f3: Add proxy contract -- Updated dependencies [29ff7462] - - @eth-optimism/core-utils@0.8.7 - -## 0.1.3 - -### Patch Changes - -- c258acd4: Update comments and style for L1 contracts - -## 0.1.2 - -### Patch Changes - -- 07a84aed: Move core-utils to deps instead of devdeps - -## 0.1.1 - -### Patch Changes - -- 1aca58c4: Initial release diff --git a/packages/contracts-bedrock/CONTRIBUTING.md b/packages/contracts-bedrock/CONTRIBUTING.md deleted file mode 100644 index 43f6a710747b3..0000000000000 --- a/packages/contracts-bedrock/CONTRIBUTING.md +++ /dev/null @@ -1,104 +0,0 @@ -# Contributing to CONTRIBUTING.md - -First off, thanks for taking the time to contribute! - -We welcome and appreciate all kinds of contributions. We ask that before contributing you please review the procedures for each type of contribution available in the [Table of Contents](#table-of-contents). This will streamline the process for both maintainers and contributors. To find ways to contribute, view the [I Want To Contribute](#i-want-to-contribute) section below. Larger contributions should [open an issue](https://github.com/ethereum-optimism/optimism/issues/new) before implementation to ensure changes don't go to waste. - -We're excited to work with you and your contributions to scaling Ethereum! - -## Table of Contents - -- [I Have a Question](#i-have-a-question) -- [I Want To Contribute](#i-want-to-contribute) -- [Reporting Bugs](#reporting-bugs) -- [Suggesting Enhancements](#suggesting-enhancements) -- [Your First Code Contribution](#your-first-code-contribution) -- [Improving The Documentation](#improving-the-documentation) -- [Deploying on Devnet](#deploying-on-devnet) -- [Tools](#tools) - -## I Have a Question - -> **Note** -> Before making an issue, please read the documentation and search the issues to see if your question has already been answered. - -If you have any questions about the smart contracts, please feel free to ask them in the Optimism discord developer channels or create a new detailed issue. - -## I Want To Contribute - -### Reporting Bugs - -**Any and all bug reports on production smart contract code should be submitted privately to the Optimism team so that we can mitigate the issue before it is exploited. Please see our security policy document [here](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md).** - -### Suggesting Enhancements - -#### Before Submitting an Enhancement - -- Read the documentation and the smart contracts themselves to see if the feature already exists. -- Perform a search in the issues to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. - -#### How Do I Submit a Good Enhancement Suggestion? - -Enhancement suggestions are tracked as [GitHub issues](https://github.com/ethereum-optimism/optimism/issues). - -- Use a **clear and descriptive title** for the issue to identify the suggestion. -- Provide a **step-by-step** description of the suggested enhancement in as many details as possible. -- Describe the **current** behavior and why the **intended** behavior you expected to see differs. At this point you can also tell which alternatives do not work for you. -- Explain why this enhancement would be useful in Optimism's smart contracts. You may also want to point out the other projects that solved it better and which could serve as inspiration. - -### Your First Code Contribution - -The best place to begin contributing is by looking through the issues with the `good first issue` label. These are issues that are relatively easy to implement and are a great way to get familiar with the codebase. - -Optimism's smart contracts are written in Solidity and we use [foundry](https://github.com/foundry-rs/foundry) as our development framework. To get started, you'll need to install several dependencies: -1. [just](https://github.com/casey/just) - 1. Make sure to `just install` -1. [foundry](https://getfoundry.sh) - 1. Foundry is built with [rust](https://www.rust-lang.org/tools/install), and this project uses a pinned version of foundry. Install the rust toolchain with `rustup`. - 1. Make sure to install the version of foundry used by `ci-builder`, defined in the `versions.json` file in the root of this repo under the `foundry` key. Once you have `foundryup` installed, there is a helper to do this: `just install-foundry` -1. [golang](https://golang.org/doc/install) -1. [python](https://www.python.org/downloads/) - -Our [Style Guide](STYLE_GUIDE.md) contains information about the project structure, syntax preferences, naming conventions, and more. Please take a look at it before submitting a PR, and let us know if you spot inconsistencies! - -Once you've read the style guide and are ready to work on your PR, there are a plethora of useful `just` scripts to know about that will help you with development. -You can run `just -l` to list them all, some of the key ones are: -1. `just build` Builds the smart contracts. -1. `just test` Runs the full `forge` test suite. -1 `just gas-snapshot` Generates the gas snapshot for the smart contracts. -1. `just semver-lock` Generates the semver lockfile. -1. `just snapshots` Generates the storage and ABI snapshots. -1. `just clean` Removes all build artifacts for `forge` and `go` compilations. -1. `just validate-spacers` Validates the positions of the storage slot spacers. -1. `just validate-deploy-configs` Validates the deployment configurations in `deploy-config` -1. `just lint` Runs the linter on the smart contracts and scripts. -1. `just pre-pr` Runs most checks, generators, and linters prior to a PR. For most PRs, this is sufficient to pass CI if everything is in order. -1. `just pre-pr-full` Runs all checks, generators, and linters prior to a PR. - -### Improving The Documentation - -Documentation improvements are more than welcome! If you see a typo or feel that a code comment describes something poorly or incorrectly, please submit a PR with a fix. - -### Deploying on Devnet - -To deploy the smart contracts on a local devnet, run `make devnet-up` in the monorepo root. For more information on the local devnet, see [dev-node](https://docs.optimism.io/chain/testing/dev-node). - -### Tools - -#### Validate Spacing - -In order to make sure that we don't accidentally overwrite storage slots, contract storage layouts are checked to make sure spacing is correct. - -This uses the `snapshots/storageLayout` directory to check contract spacing. Run `just validate-spacers` to check the spacing of all contracts. - -#### Gas Snapshots - -We use forge's `gas-snapshot` subcommand to produce a gas snapshot for tests in `Benchmark.t.sol`. CI will check that the gas snapshot has been updated properly when it runs, so make sure to run `just gas-snapshot`! - -#### Semver Locking - -Many of our smart contracts are semantically versioned. To make sure that changes are not made to a contract without deliberately bumping its version, we commit to the source code and the creation bytecode of its dependencies in a lockfile. Consult the [Style Guide](./STYLE_GUIDE.md#Versioning) for more information about how our contracts are versioned. - -#### Storage Snapshots - -Due to the many proxied contracts in Optimism's protocol, we automate tracking the diff to storage layouts of the contracts in the project. This is to ensure that we don't break a proxy by upgrading its implementation to a contract with a different storage layout. To generate the storage lockfile, run `just snapshots`. diff --git a/packages/contracts-bedrock/README.md b/packages/contracts-bedrock/README.md index a4a718eeac47e..24212f38ff341 100644 --- a/packages/contracts-bedrock/README.md +++ b/packages/contracts-bedrock/README.md @@ -66,22 +66,22 @@ See the [Optimism Developer Docs](https://docs.optimism.io/chain/addresses) for ### Contributing Guide Contributions to the OP Stack are always welcome. -Please refer to the [CONTRIBUTING.md](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/CONTRIBUTING.md) for more information about how to contribute to the OP Stack smart contracts. +Please refer to the [CONTRIBUTING.md](../../CONTRIBUTING.md) for more information about how to contribute to the OP Stack smart contracts. ### Style Guide -OP Stack smart contracts should be written according to the [STYLE_GUIDE.md](./STYLE_GUIDE.md) found within this repository. +OP Stack smart contracts should be written according to the [STYLE_GUIDE.md](./meta/STYLE_GUIDE.md) found within this repository. Maintaining a consistent code style makes code easier to review and maintain, ultimately making the development process safer. ### Contract Interfaces OP Stack smart contracts use contract interfaces in a relatively unique way. Please refer to -[INTERFACES.md](./INTERFACES.md) to read more about how the OP Stack uses contract interfaces. +[INTERFACES.md](./meta/INTERFACES.md) to read more about how the OP Stack uses contract interfaces. ### Solidity Versioning OP Stack smart contracts are designed to utilize a single, consistent Solidity version. Please -refer to [SOLIDITY_UPGRADES.md](./SOLIDITY_UPGRADES.md) to understand the process for updating to +refer to [SOLIDITY_UPGRADES.md](./meta/SOLIDITY_UPGRADES.md) to understand the process for updating to newer Solidity versions. ## Deployment @@ -138,9 +138,9 @@ Create or modify a file `.json` inside of the [`deploy-config`](./ Use the env var `DEPLOY_CONFIG_PATH` to use a particular deploy config file at runtime. The script will read the latest active fork from the deploy config and the L2 genesis allocs generated will be -compatible with this fork. The automatically detected fork can be overwritten by setting the environment variable -`FORK` either to the lower-case fork name (currently `delta`, `ecotone`, `fjord`, or `granite`) or to `latest`, which -will select the latest fork available (currently `granite`). +compatible with this fork. The automatically detected fork can be overwritten by setting the environment variable `FORK` +either to the lower-case fork name (currently `delta`, `ecotone`, `fjord`, `granite`, or `holocene`) or to `latest`, +which will select the latest fork available (currently `holocene`). By default, the script will dump the L2 genesis allocs of the detected or selected fork only, to the file at `STATE_DUMP_PATH`. The optional environment variable `OUTPUT_MODE` allows to modify this behavior by setting it to one of the following values: diff --git a/packages/contracts-bedrock/deploy-config/devnetL1-template.json b/packages/contracts-bedrock/deploy-config/devnetL1-template.json index 1ca5fbfe4dd63..0cb7a83f26560 100644 --- a/packages/contracts-bedrock/deploy-config/devnetL1-template.json +++ b/packages/contracts-bedrock/deploy-config/devnetL1-template.json @@ -6,7 +6,7 @@ "sequencerWindowSize": 200, "channelTimeout": 120, "p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", - "batchInboxAddress": "0xff00000000000000000000000000000000000901", + "batchInboxAddress": "0x00289C189bEE4E70334629f04Cd5eD602B6600eB", "batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "l1StartingBlockTag": "earliest", "l2OutputOracleSubmissionInterval": 10, @@ -48,20 +48,22 @@ "l2GenesisDeltaTimeOffset": "0x0", "l2GenesisEcotoneTimeOffset": "0x0", "l2GenesisFjordTimeOffset": "0x0", + "l2GenesisGraniteTimeOffset": "0x0", + "l2GenesisBlobTimeOffset": "0x0", "l1CancunTimeOffset": "0x0", "systemConfigStartBlock": 0, "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000001", "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000001", "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", "faultGameMaxDepth": 50, - "faultGameClockExtension": 0, - "faultGameMaxClockDuration": 1200, + "faultGameClockExtension": 800, + "faultGameMaxClockDuration": 2000, "faultGameGenesisBlock": 0, "faultGameGenesisOutputRoot": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", "faultGameSplitDepth": 14, "faultGameWithdrawalDelay": 604800, "preimageOracleMinProposalSize": 10000, - "preimageOracleChallengePeriod": 120, + "preimageOracleChallengePeriod": 400, "proofMaturityDelaySeconds": 12, "disputeGameFinalityDelaySeconds": 6, "respectedGameType": 0, @@ -71,5 +73,7 @@ "daChallengeWindow": 16, "daResolveWindow": 16, "daBondSize": 1000000, + "useSoulGasToken": true, + "isSoulBackedByNative": true, "daResolverRefundPercentage": 0 } diff --git a/packages/contracts-bedrock/deploy-config/hardhat.json b/packages/contracts-bedrock/deploy-config/hardhat.json index 6dcbb299d1deb..29b40e41c33a1 100644 --- a/packages/contracts-bedrock/deploy-config/hardhat.json +++ b/packages/contracts-bedrock/deploy-config/hardhat.json @@ -11,7 +11,7 @@ "sequencerWindowSize": 15, "channelTimeout": 40, "p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", - "batchInboxAddress": "0xff00000000000000000000000000000000000000", + "batchInboxAddress": "0x00289C189bEE4E70334629f04Cd5eD602B6600eB", "batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "l2OutputOracleSubmissionInterval": 6, "l2OutputOracleStartingTimestamp": 1, @@ -63,4 +63,4 @@ "daResolveWindow": 100, "daBondSize": 1000, "daResolverRefundPercentage": 50 -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/deploy-config/sepolia-devnet-0.json b/packages/contracts-bedrock/deploy-config/sepolia-devnet-0.json index 7dd5df431cd14..8cc17a8f668d6 100644 --- a/packages/contracts-bedrock/deploy-config/sepolia-devnet-0.json +++ b/packages/contracts-bedrock/deploy-config/sepolia-devnet-0.json @@ -15,8 +15,6 @@ "l2OutputOracleStartingBlockNumber": 0, "l2OutputOracleProposer": "0x95014c45078354ff839f14192228108eac82e00a", "l2OutputOracleChallenger": "0x8c20c40180751d93e939dddee3517ae0d1ebead2", - "cliqueSignerAddress": "0x0000000000000000000000000000000000000000", - "l1UseClique": false, "l1BlockTime": 12, "l1GenesisBlockTimestamp": "0x0", "l1GenesisBlockNonce": "0x0", @@ -39,7 +37,6 @@ "l2GenesisRegolithTimeOffset": "0x0", "l2GenesisCanyonTimeOffset": "0x0", "l2GenesisDeltaTimeOffset": "0x0", - "l2GenesisBlockExtraData": null, "proxyAdminOwner": "0x8c20c40180751d93e939dddee3517ae0d1ebead2", "finalSystemOwner": "0x8c20c40180751d93e939dddee3517ae0d1ebead2", "superchainConfigGuardian": "0x8c20c40180751d93e939dddee3517ae0d1ebead2", @@ -66,7 +63,7 @@ "eip1559Denominator": 250, "eip1559DenominatorCanyon": 250, "systemConfigStartBlock": 4071248, - "faultGameAbsolutePrestate": "0x0385c3f8ee78491001d92b90b07d0cf387b7b52ab9b83b4d87c994e92cf823ba", + "faultGameAbsolutePrestate": "0x03925193e3e89f87835bbdf3a813f60b2aa818a36bbe71cd5d8fd7e79f5e8afe", "faultGameMaxDepth": 73, "faultGameClockExtension": 3600, "faultGameMaxClockDuration": 14400, diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index f9e44d6a5b634..b77e7bca43129 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -1,15 +1,23 @@ ################################################################ -# PROFILE: DEFAULT (Local) # +# PROFILE: DEFAULT (local) # ################################################################ [profile.default] -# Compilation settings src = 'src' out = 'forge-artifacts' script = 'scripts' +build_info_path = 'artifacts/build-info' +snapshots = 'notarealpath' # workaround for foundry#9477 + optimizer = true optimizer_runs = 999999 + +extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] +bytecode_hash = 'none' +ast = true +evm_version = 'cancun' + remappings = [ '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts', @@ -17,28 +25,15 @@ remappings = [ '@rari-capital/solmate/=lib/solmate', '@lib-keccak/=lib/lib-keccak/contracts/lib', '@solady/=lib/solady/src', + '@solady-v0.0.245/=lib/solady-v0.0.245/src', 'forge-std/=lib/forge-std/src', 'ds-test/=lib/forge-std/lib/ds-test/src', 'safe-contracts/=lib/safe-contracts/contracts', 'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src', - 'gelato/=lib/automate/contracts' + 'gelato/=lib/automate/contracts', + 'interfaces/=interfaces' ] -extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] -bytecode_hash = 'none' -build_info_path = 'artifacts/build-info' -ast = true -evm_version = "cancun" -# 5159 error code is selfdestruct error code -ignored_error_codes = ["transient-storage", "code-size", "init-code-size", 5159] -# We set the gas limit to max int64 to avoid running out of gas during testing, since the default -# gas limit is 1B and some of our tests require more gas than that, such as `test_callWithMinGas_noLeakageLow_succeeds`. -# We use this gas limit since it was the default gas limit prior to https://github.com/foundry-rs/foundry/pull/8274. -# Due to toml-rs limitations, if you increase the gas limit above this value it must be a string. -gas_limit = 9223372036854775807 - -# Test / Script Runner Settings -ffi = true fs_permissions = [ { access='read-write', path='./.resource-metering.csv' }, { access='read-write', path='./snapshots/' }, @@ -47,12 +42,21 @@ fs_permissions = [ { access='read', path='./deploy-config-periphery/' }, { access='read', path='./broadcast/' }, { access='read', path = './forge-artifacts/' }, - { access='write', path='./semver-lock.json' }, { access='read-write', path='./.testdata/' }, { access='read', path='./kout-deployment' }, { access='read', path='./test/fixtures' }, ] -libs = ["node_modules", "lib"] + +# 5159 error code is selfdestruct error code +ignored_error_codes = ["transient-storage", "code-size", "init-code-size", 5159] +ffi = true + +# We set the gas limit to max int64 to avoid running out of gas during testing, since the default +# gas limit is 1B and some of our tests require more gas than that, such as +# test_callWithMinGas_noLeakageLow_succeeds. We use this gas limit since it was the default gas +# limit prior to https://github.com/foundry-rs/foundry/pull/8274. Due to toml-rs limitations, if +# you increase the gas limit above this value it must be a string. +gas_limit = 9223372036854775807 [fuzz] runs = 64 @@ -67,19 +71,39 @@ wrap_comments=true # PROFILE: CI # ################################################################ -[profile.ci] -fuzz = { runs = 512 } +[profile.ci.fuzz] +runs = 512 [profile.ci.invariant] runs = 256 depth = 32 -[profile.ciheavy] -fuzz = { runs = 10000 } +################################################################ +# PROFILE: CICOVERAGE # +################################################################ + +[profile.cicoverage] +optimizer = false + +[profile.cicoverage.fuzz] +runs = 512 + +[profile.cicoverage.invariant] +runs = 256 +depth = 32 + +################################################################ +# PROFILE: CIHEAVY # +################################################################ + +[profile.ciheavy.fuzz] +runs = 20000 +timeout = 600 [profile.ciheavy.invariant] runs = 128 depth = 512 +timeout = 600 ################################################################ # PROFILE: LITE # @@ -91,13 +115,6 @@ optimizer = false ################################################################ # PROFILE: KONTROL # ################################################################ -# See test/kontrol/README.md for an explanation of how the profiles are configured - -[profile.kdeploy] -src = 'src' -out = 'kout-deployment' -test = 'test/kontrol' -script = 'scripts-kontrol' [profile.kprove] src = 'test/kontrol/proofs' diff --git a/packages/contracts-bedrock/src/L1/interfaces/IDataAvailabilityChallenge.sol b/packages/contracts-bedrock/interfaces/L1/IDataAvailabilityChallenge.sol similarity index 100% rename from packages/contracts-bedrock/src/L1/interfaces/IDataAvailabilityChallenge.sol rename to packages/contracts-bedrock/interfaces/L1/IDataAvailabilityChallenge.sol diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol similarity index 68% rename from packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessenger.sol rename to packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol index 8a6de84e2c9df..ae9aa75ef155f 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; interface IL1CrossDomainMessenger is ICrossDomainMessenger { function PORTAL() external view returns (IOptimismPortal); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessengerV160.sol b/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessengerV160.sol similarity index 76% rename from packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessengerV160.sol rename to packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessengerV160.sol index d81bb5d255657..09d4e604fb8f3 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessengerV160.sol +++ b/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessengerV160.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; /// @notice This interface corresponds to the op-contracts/v1.6.0 release of the L1CrossDomainMessenger /// contract, which has a semver of 2.3.0 as specified in diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1ERC721Bridge.sol b/packages/contracts-bedrock/interfaces/L1/IL1ERC721Bridge.sol similarity index 82% rename from packages/contracts-bedrock/src/L1/interfaces/IL1ERC721Bridge.sol rename to packages/contracts-bedrock/interfaces/L1/IL1ERC721Bridge.sol index 51356bc8d3468..33756dbc49f1d 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1ERC721Bridge.sol +++ b/packages/contracts-bedrock/interfaces/L1/IL1ERC721Bridge.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IERC721Bridge } from "src/universal/interfaces/IERC721Bridge.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { IERC721Bridge } from "interfaces/universal/IERC721Bridge.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; interface IL1ERC721Bridge is IERC721Bridge { function bridgeERC721( diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridge.sol b/packages/contracts-bedrock/interfaces/L1/IL1StandardBridge.sol similarity index 87% rename from packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridge.sol rename to packages/contracts-bedrock/interfaces/L1/IL1StandardBridge.sol index 816436cf1084a..847ea76b44d2e 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridge.sol +++ b/packages/contracts-bedrock/interfaces/L1/IL1StandardBridge.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; +import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; interface IL1StandardBridge is IStandardBridge { event ERC20DepositInitiated( diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridgeV160.sol b/packages/contracts-bedrock/interfaces/L1/IL1StandardBridgeV160.sol similarity index 88% rename from packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridgeV160.sol rename to packages/contracts-bedrock/interfaces/L1/IL1StandardBridgeV160.sol index b382c4f1ad6dc..8d212de24486d 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridgeV160.sol +++ b/packages/contracts-bedrock/interfaces/L1/IL1StandardBridgeV160.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; +import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; /// @notice This interface corresponds to the op-contracts/v1.6.0 release of the L1StandardBridge /// contract, which has a semver of 2.1.0 as specified in diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL2OutputOracle.sol b/packages/contracts-bedrock/interfaces/L1/IL2OutputOracle.sol similarity index 100% rename from packages/contracts-bedrock/src/L1/interfaces/IL2OutputOracle.sol rename to packages/contracts-bedrock/interfaces/L1/IL2OutputOracle.sol diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal.sol similarity index 93% rename from packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal.sol rename to packages/contracts-bedrock/interfaces/L1/IOptimismPortal.sol index b9035a6e51432..086a9ebc7bd85 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; interface IOptimismPortal { error BadTarget(); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol similarity index 93% rename from packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol rename to packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol index 91f09d714314d..74ba7b4790e95 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; interface IOptimismPortal2 { error AlreadyFinalized(); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol similarity index 92% rename from packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol rename to packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol index 521c7232e125b..58fe5eff5dcab 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ConfigType } from "src/L2/L1BlockInterop.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; interface IOptimismPortalInterop { error AlreadyFinalized(); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IProtocolVersions.sol b/packages/contracts-bedrock/interfaces/L1/IProtocolVersions.sol similarity index 100% rename from packages/contracts-bedrock/src/L1/interfaces/IProtocolVersions.sol rename to packages/contracts-bedrock/interfaces/L1/IProtocolVersions.sol diff --git a/packages/contracts-bedrock/src/L1/interfaces/IResourceMetering.sol b/packages/contracts-bedrock/interfaces/L1/IResourceMetering.sol similarity index 100% rename from packages/contracts-bedrock/src/L1/interfaces/IResourceMetering.sol rename to packages/contracts-bedrock/interfaces/L1/IResourceMetering.sol diff --git a/packages/contracts-bedrock/src/L1/interfaces/ISuperchainConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISuperchainConfig.sol similarity index 100% rename from packages/contracts-bedrock/src/L1/interfaces/ISuperchainConfig.sol rename to packages/contracts-bedrock/interfaces/L1/ISuperchainConfig.sol diff --git a/packages/contracts-bedrock/src/L1/interfaces/ISystemConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol similarity index 91% rename from packages/contracts-bedrock/src/L1/interfaces/ISystemConfig.sol rename to packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol index a7c5434d048b0..904375167f48d 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/ISystemConfig.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol @@ -1,15 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; /// @notice This interface corresponds to the Custom Gas Token version of the SystemConfig contract. interface ISystemConfig { enum UpdateType { BATCHER, - GAS_CONFIG, + FEE_SCALARS, GAS_LIMIT, - UNSAFE_BLOCK_SIGNER + UNSAFE_BLOCK_SIGNER, + EIP_1559_PARAMS } struct Addresses { @@ -42,6 +43,8 @@ interface ISystemConfig { function blobbasefeeScalar() external view returns (uint32); function disputeGameFactory() external view returns (address addr_); function gasLimit() external view returns (uint64); + function eip1559Denominator() external view returns (uint32); + function eip1559Elasticity() external view returns (uint32); function gasPayingToken() external view returns (address addr_, uint8 decimals_); function gasPayingTokenName() external view returns (string memory name_); function gasPayingTokenSymbol() external view returns (string memory symbol_); @@ -75,6 +78,7 @@ interface ISystemConfig { function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external; function setGasLimit(uint64 _gasLimit) external; function setUnsafeBlockSigner(address _unsafeBlockSigner) external; + function setEIP1559Params(uint32 _denominator, uint32 _elasticity) external; function startBlock() external view returns (uint256 startBlock_); function transferOwnership(address newOwner) external; // nosemgrep function unsafeBlockSigner() external view returns (address addr_); diff --git a/packages/contracts-bedrock/src/L1/interfaces/ISystemConfigInterop.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol similarity index 91% rename from packages/contracts-bedrock/src/L1/interfaces/ISystemConfigInterop.sol rename to packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol index fffbd3cb66814..4cf4a06f943fe 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/ISystemConfigInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; interface ISystemConfigInterop { event ConfigUpdate(uint256 indexed version, ISystemConfig.UpdateType indexed updateType, bytes data); @@ -25,6 +25,8 @@ interface ISystemConfigInterop { function blobbasefeeScalar() external view returns (uint32); function disputeGameFactory() external view returns (address addr_); function gasLimit() external view returns (uint64); + function eip1559Denominator() external view returns (uint32); + function eip1559Elasticity() external view returns (uint32); function gasPayingToken() external view returns (address addr_, uint8 decimals_); function gasPayingTokenName() external view returns (string memory name_); function gasPayingTokenSymbol() external view returns (string memory symbol_); @@ -46,6 +48,7 @@ interface ISystemConfigInterop { function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external; function setGasLimit(uint64 _gasLimit) external; function setUnsafeBlockSigner(address _unsafeBlockSigner) external; + function setEIP1559Params(uint32 _denominator, uint32 _elasticity) external; function startBlock() external view returns (uint256 startBlock_); function transferOwnership(address newOwner) external; // nosemgrep function unsafeBlockSigner() external view returns (address addr_); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IBaseFeeVault.sol b/packages/contracts-bedrock/interfaces/L2/IBaseFeeVault.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IBaseFeeVault.sol rename to packages/contracts-bedrock/interfaces/L2/IBaseFeeVault.sol diff --git a/packages/contracts-bedrock/interfaces/L2/ICrossL2Inbox.sol b/packages/contracts-bedrock/interfaces/L2/ICrossL2Inbox.sol new file mode 100644 index 0000000000000..bbd8c09afd8cc --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/ICrossL2Inbox.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +struct Identifier { + address origin; + uint256 blockNumber; + uint256 logIndex; + uint256 timestamp; + uint256 chainId; +} + +/// @title ICrossL2Inbox +/// @notice Interface for the CrossL2Inbox contract. +interface ICrossL2Inbox { + error ReentrantCall(); + + /// @notice Thrown when the caller is not DEPOSITOR_ACCOUNT when calling `setInteropStart()` + error NotDepositor(); + + /// @notice Thrown when attempting to set interop start when it's already set. + error InteropStartAlreadySet(); + + /// @notice Thrown when a non-written transient storage slot is attempted to be read from. + error NotEntered(); + + /// @notice Thrown when trying to execute a cross chain message with an invalid Identifier timestamp. + error InvalidTimestamp(); + + /// @notice Thrown when trying to execute a cross chain message with an invalid Identifier chain ID. + error InvalidChainId(); + + /// @notice Thrown when trying to execute a cross chain message and the target call fails. + error TargetCallFailed(); + + /// @notice Thrown when trying to execute a cross chain message on a deposit transaction. + error NoExecutingDeposits(); + + event ExecutingMessage(bytes32 indexed msgHash, Identifier id); + + function version() external view returns (string memory); + + /// @notice Returns the interop start timestamp. + /// @return interopStart_ interop start timestamp. + function interopStart() external view returns (uint256 interopStart_); + + /// @notice Returns the origin address of the Identifier. + function origin() external view returns (address); + + /// @notice Returns the block number of the Identifier. + function blockNumber() external view returns (uint256); + + /// @notice Returns the log index of the Identifier. + function logIndex() external view returns (uint256); + + /// @notice Returns the timestamp of the Identifier. + function timestamp() external view returns (uint256); + + /// @notice Returns the chain ID of the Identifier. + function chainId() external view returns (uint256); + + function setInteropStart() external; + + /// @notice Executes a cross chain message on the destination chain. + /// @param _id An Identifier pointing to the initiating message. + /// @param _target Account that is called with _msg. + /// @param _message The message payload, matching the initiating message. + function executeMessage(Identifier calldata _id, address _target, bytes calldata _message) external payable; + + /// @notice Validates a cross chain message on the destination chain + /// and emits an ExecutingMessage event. This function is useful + /// for applications that understand the schema of the _message payload and want to + /// process it in a custom way. + /// @param _id Identifier of the message. + /// @param _msgHash Hash of the message payload to call target with. + function validateMessage(Identifier calldata _id, bytes32 _msgHash) external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IDependencySet.sol b/packages/contracts-bedrock/interfaces/L2/IDependencySet.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IDependencySet.sol rename to packages/contracts-bedrock/interfaces/L2/IDependencySet.sol diff --git a/packages/contracts-bedrock/interfaces/L2/IERC7802.sol b/packages/contracts-bedrock/interfaces/L2/IERC7802.sol new file mode 100644 index 0000000000000..38c92d01d8d2b --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/IERC7802.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; + +/// @title IERC7802 +/// @notice Defines the interface for crosschain ERC20 transfers. +interface IERC7802 is IERC165 { + /// @notice Emitted when a crosschain transfer mints tokens. + /// @param to Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + /// @param sender Address of the account that finilized the crosschain transfer. + event CrosschainMint(address indexed to, uint256 amount, address indexed sender); + + /// @notice Emitted when a crosschain transfer burns tokens. + /// @param from Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + /// @param sender Address of the account that initiated the crosschain transfer. + event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); + + /// @notice Mint tokens through a crosschain transfer. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function crosschainMint(address _to, uint256 _amount) external; + + /// @notice Burn tokens through a crosschain transfer. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function crosschainBurn(address _from, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IETHLiquidity.sol b/packages/contracts-bedrock/interfaces/L2/IETHLiquidity.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IETHLiquidity.sol rename to packages/contracts-bedrock/interfaces/L2/IETHLiquidity.sol diff --git a/packages/contracts-bedrock/src/L2/interfaces/IFeeVault.sol b/packages/contracts-bedrock/interfaces/L2/IFeeVault.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IFeeVault.sol rename to packages/contracts-bedrock/interfaces/L2/IFeeVault.sol diff --git a/packages/contracts-bedrock/src/L2/interfaces/IGasPriceOracle.sol b/packages/contracts-bedrock/interfaces/L2/IGasPriceOracle.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IGasPriceOracle.sol rename to packages/contracts-bedrock/interfaces/L2/IGasPriceOracle.sol diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol similarity index 92% rename from packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol rename to packages/contracts-bedrock/interfaces/L2/IL1Block.sol index a43b3c7c39639..1e7cb415002f4 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol @@ -38,4 +38,7 @@ interface IL1Block { function version() external pure returns (string memory); function __constructor__() external; + + function historySize() external pure returns (uint256); + function blockHash(uint256 _historyNumber) external view returns (bytes32); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL1BlockInterop.sol b/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol similarity index 94% rename from packages/contracts-bedrock/src/L2/interfaces/IL1BlockInterop.sol rename to packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol index dd72e3fa6f894..b9fa78733e788 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL1BlockInterop.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol @@ -57,4 +57,7 @@ interface IL1BlockInterop { function version() external pure returns (string memory); function __constructor__() external; + + function historySize() external pure returns (uint256); + function blockHash(uint256 _historyNumber) external view returns (bytes32); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL1FeeVault.sol b/packages/contracts-bedrock/interfaces/L2/IL1FeeVault.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IL1FeeVault.sol rename to packages/contracts-bedrock/interfaces/L2/IL1FeeVault.sol diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2CrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/L2/IL2CrossDomainMessenger.sol similarity index 83% rename from packages/contracts-bedrock/src/L2/interfaces/IL2CrossDomainMessenger.sol rename to packages/contracts-bedrock/interfaces/L2/IL2CrossDomainMessenger.sol index 1cb49f674ec0b..c35b664b94701 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL2CrossDomainMessenger.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; interface IL2CrossDomainMessenger is ICrossDomainMessenger { function MESSAGE_VERSION() external view returns (uint16); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2ERC721Bridge.sol b/packages/contracts-bedrock/interfaces/L2/IL2ERC721Bridge.sol similarity index 86% rename from packages/contracts-bedrock/src/L2/interfaces/IL2ERC721Bridge.sol rename to packages/contracts-bedrock/interfaces/L2/IL2ERC721Bridge.sol index a760ce1d803c9..1ca3d778e7a21 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2ERC721Bridge.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL2ERC721Bridge.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IERC721Bridge } from "src/universal/interfaces/IERC721Bridge.sol"; +import { IERC721Bridge } from "interfaces/universal/IERC721Bridge.sol"; interface IL2ERC721Bridge is IERC721Bridge { function finalizeBridgeERC721( diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridge.sol b/packages/contracts-bedrock/interfaces/L2/IL2StandardBridge.sol similarity index 93% rename from packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridge.sol rename to packages/contracts-bedrock/interfaces/L2/IL2StandardBridge.sol index 9f9ce1a85621d..2ad5f1bb5cc2c 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridge.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL2StandardBridge.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; +import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; interface IL2StandardBridge is IStandardBridge { event DepositFinalized( diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol b/packages/contracts-bedrock/interfaces/L2/IL2StandardBridgeInterop.sol similarity index 88% rename from packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol rename to packages/contracts-bedrock/interfaces/L2/IL2StandardBridgeInterop.sol index ed4ec1cef5190..97b24a2b258c3 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL2StandardBridgeInterop.sol @@ -1,14 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; - -interface IMintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} +import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; interface IL2StandardBridgeInterop is IStandardBridge { error InvalidDecimals(); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2ToL1MessagePasser.sol b/packages/contracts-bedrock/interfaces/L2/IL2ToL1MessagePasser.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IL2ToL1MessagePasser.sol rename to packages/contracts-bedrock/interfaces/L2/IL2ToL1MessagePasser.sol diff --git a/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol new file mode 100644 index 0000000000000..89311cf18f01c --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +struct Identifier { + address origin; + uint256 blockNumber; + uint256 logIndex; + uint256 timestamp; + uint256 chainId; +} + +/// @title IL2ToL2CrossDomainMessenger +/// @notice Interface for the L2ToL2CrossDomainMessenger contract. +interface IL2ToL2CrossDomainMessenger { + /// @notice Thrown when a non-written slot in transient storage is attempted to be read from. + error NotEntered(); + + /// @notice Thrown when attempting to relay a message where payload origin is not L2ToL2CrossDomainMessenger. + error IdOriginNotL2ToL2CrossDomainMessenger(); + + /// @notice Thrown when the payload provided to the relay is not a SentMessage event. + error EventPayloadNotSentMessage(); + + /// @notice Thrown when attempting to send a message to the chain that the message is being sent from. + error MessageDestinationSameChain(); + + /// @notice Thrown when attempting to relay a message whose destination chain is not the chain relaying it. + error MessageDestinationNotRelayChain(); + + /// @notice Thrown when attempting to relay a message whose target is CrossL2Inbox. + error MessageTargetCrossL2Inbox(); + + /// @notice Thrown when attempting to relay a message whose target is L2ToL2CrossDomainMessenger. + error MessageTargetL2ToL2CrossDomainMessenger(); + + /// @notice Thrown when attempting to relay a message that has already been relayed. + error MessageAlreadyRelayed(); + + /// @notice Thrown when a reentrant call is detected. + error ReentrantCall(); + + /// @notice Thrown when a call to the target contract during message relay fails. + error TargetCallFailed(); + + /// @notice Thrown when attempting to use a chain ID that is not in the dependency set. + error InvalidChainId(); + + /// @notice Emitted whenever a message is sent to a destination + /// @param destination Chain ID of the destination chain. + /// @param target Target contract or wallet address. + /// @param messageNonce Nonce associated with the messsage sent + /// @param sender Address initiating this message call + /// @param message Message payload to call target with. + event SentMessage( + uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message + ); + + /// @notice Emitted whenever a message is successfully relayed on this chain. + /// @param source Chain ID of the source chain. + /// @param messageNonce Nonce associated with the messsage sent + /// @param messageHash Hash of the message that was relayed. + event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash); + + function version() external view returns (string memory); + + /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only + /// be present in this mapping if it has successfully been relayed on this chain, and + /// can therefore not be relayed again. + /// @return Returns true if the message corresponding to the `_msgHash` was successfully relayed. + function successfulMessages(bytes32) external view returns (bool); + + /// @notice Retrieves the next message nonce. Message version will be added to the upper two + /// bytes of the message nonce. Message version allows us to treat messages as having + /// different structures. + /// @return Nonce of the next message to be sent, with added message version. + function messageNonce() external view returns (uint256); + + /// @notice Retrieves the sender of the current cross domain message. + /// @return sender_ Address of the sender of the current cross domain message. + function crossDomainMessageSender() external view returns (address sender_); + + /// @notice Retrieves the source of the current cross domain message. + /// @return source_ Chain ID of the source of the current cross domain message. + function crossDomainMessageSource() external view returns (uint256 source_); + + /// @notice Retrieves the context of the current cross domain message. If not entered, reverts. + /// @return sender_ Address of the sender of the current cross domain message. + /// @return source_ Chain ID of the source of the current cross domain message. + function crossDomainMessageContext() external view returns (address sender_, uint256 source_); + + /// @notice Sends a message to some target address on a destination chain. Note that if the call + /// always reverts, then the message will be unrelayable, and any ETH sent will be + /// permanently locked. The same will occur if the target on the other chain is + /// considered unsafe (see the _isUnsafeTarget() function). + /// @param _destination Chain ID of the destination chain. + /// @param _target Target contract or wallet address. + /// @param _message Message to trigger the target address with. + /// @return msgHash_ The hash of the message being sent, which can be used for tracking whether + /// the message has successfully been relayed. + function sendMessage(uint256 _destination, address _target, bytes calldata _message) external returns (bytes32); + + /// @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only + /// be executed via cross-chain call from the other messenger OR if the message was + /// already received once and is currently being replayed. + /// @param _id Identifier of the SentMessage event to be relayed + /// @param _sentMessage Message payload of the `SentMessage` event + function relayMessage(Identifier calldata _id, bytes calldata _sentMessage) external payable; + + function messageVersion() external view returns (uint16); + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L2/IMintableAndBurnableERC20.sol b/packages/contracts-bedrock/interfaces/L2/IMintableAndBurnableERC20.sol new file mode 100644 index 0000000000000..166fa0c8077f9 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/IMintableAndBurnableERC20.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title IMintableAndBurnableERC20 +/// @notice Interface for mintable and burnable ERC20 tokens. +interface IMintableAndBurnableERC20 is IERC20 { + /// @notice Mints `_amount` of tokens to `_to`. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function mint(address _to, uint256 _amount) external; + + /// @notice Burns `_amount` of tokens from `_from`. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function burn(address _from, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismERC20Factory.sol b/packages/contracts-bedrock/interfaces/L2/IOptimismERC20Factory.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/IOptimismERC20Factory.sol rename to packages/contracts-bedrock/interfaces/L2/IOptimismERC20Factory.sol diff --git a/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20.sol new file mode 100644 index 0000000000000..58dea0df33a15 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Interfaces +import { ISuperchainERC20 } from "interfaces/L2/ISuperchainERC20.sol"; + +/// @title IOptimismSuperchainERC20 +/// @notice This interface is available on the OptimismSuperchainERC20 contract. +interface IOptimismSuperchainERC20 is ISuperchainERC20 { + error ZeroAddress(); + error InvalidInitialization(); + error NotInitializing(); + + event Initialized(uint64 version); + + event Mint(address indexed to, uint256 amount); + + event Burn(address indexed from, uint256 amount); + + function initialize(address _remoteToken, string memory _name, string memory _symbol, uint8 _decimals) external; + + function mint(address _to, uint256 _amount) external; + + function burn(address _from, uint256 _amount) external; + + function remoteToken() external view returns (address); + + function supportsInterface(bytes4 _interfaceId) external view returns (bool); + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20Beacon.sol b/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20Beacon.sol new file mode 100644 index 0000000000000..850cbb6f1e203 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20Beacon.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "interfaces/universal/ISemver.sol"; + +/// @title IOptimismSuperchainERC20Beacon +/// @notice Interface for the OptimismSuperchainERC20Beacon contract +interface IOptimismSuperchainERC20Beacon is ISemver { + function implementation() external pure returns (address); + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20Factory.sol similarity index 65% rename from packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol rename to packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20Factory.sol index dc21a3d51ad7d..2c23d575326b4 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/interfaces/L2/IOptimismSuperchainERC20Factory.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { IOptimismERC20Factory } from "interfaces/L2/IOptimismERC20Factory.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title IOptimismSuperchainERC20Factory /// @notice Interface for the OptimismSuperchainERC20Factory contract @@ -11,8 +11,6 @@ interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { address indexed superchainToken, address indexed remoteToken, address deployer ); - function deployments(address _superchainToken) external view override returns (address remoteToken_); - function version() external view override returns (string memory); function deploy( address _remoteToken, string memory _name, diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISequencerFeeVault.sol b/packages/contracts-bedrock/interfaces/L2/ISequencerFeeVault.sol similarity index 100% rename from packages/contracts-bedrock/src/L2/interfaces/ISequencerFeeVault.sol rename to packages/contracts-bedrock/interfaces/L2/ISequencerFeeVault.sol diff --git a/packages/contracts-bedrock/interfaces/L2/ISoulGasToken.sol b/packages/contracts-bedrock/interfaces/L2/ISoulGasToken.sol new file mode 100644 index 0000000000000..0de6c203f4b02 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/ISoulGasToken.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ISoulGasToken +/// @notice The interface for the SoulGasToken. +interface ISoulGasToken { + function initialize(string memory _name, string memory _symbol, address _owner) external; + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L2/ISuperchainERC20.sol b/packages/contracts-bedrock/interfaces/L2/ISuperchainERC20.sol new file mode 100644 index 0000000000000..243f6f7ba49b3 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/ISuperchainERC20.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Interfaces +import { IERC7802 } from "interfaces/L2/IERC7802.sol"; +import { IERC20Solady as IERC20 } from "interfaces/vendor/IERC20Solady.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +/// @title ISuperchainERC20 +/// @notice This interface is available on the SuperchainERC20 contract. +/// @dev This interface is needed for the abstract SuperchainERC20 implementation but is not part of the standard +interface ISuperchainERC20 is IERC7802, IERC20, ISemver { + error Unauthorized(); + + function supportsInterface(bytes4 _interfaceId) external view returns (bool); + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L2/ISuperchainTokenBridge.sol b/packages/contracts-bedrock/interfaces/L2/ISuperchainTokenBridge.sol new file mode 100644 index 0000000000000..0794fa83c996f --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/ISuperchainTokenBridge.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "interfaces/universal/ISemver.sol"; + +/// @title ISuperchainTokenBridge +/// @notice Interface for the SuperchainTokenBridge contract. +interface ISuperchainTokenBridge is ISemver { + error ZeroAddress(); + error Unauthorized(); + error InvalidCrossDomainSender(); + error InvalidERC7802(); + + event SendERC20( + address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination + ); + + event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); + + function sendERC20( + address _token, + address _to, + uint256 _amount, + uint256 _chainId + ) + external + returns (bytes32 msgHash_); + + function relayERC20(address _token, address _from, address _to, uint256 _amount) external; + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L2/ISuperchainWETH.sol b/packages/contracts-bedrock/interfaces/L2/ISuperchainWETH.sol new file mode 100644 index 0000000000000..a6b6ef3ce7bf7 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/ISuperchainWETH.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IWETH98 } from "interfaces/universal/IWETH98.sol"; +import { IERC7802 } from "interfaces/L2/IERC7802.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +interface ISuperchainWETH is IWETH98, IERC7802, ISemver { + error Unauthorized(); + error NotCustomGasToken(); + error InvalidCrossDomainSender(); + error ZeroAddress(); + + event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination); + + event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source); + + function balanceOf(address src) external view returns (uint256); + function withdraw(uint256 _amount) external; + function supportsInterface(bytes4 _interfaceId) external view returns (bool); + function sendETH(address _to, uint256 _chainId) external payable returns (bytes32 msgHash_); + function relayETH(address _from, address _to, uint256 _amount) external; + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS.sol b/packages/contracts-bedrock/interfaces/cannon/IMIPS.sol similarity index 51% rename from packages/contracts-bedrock/src/cannon/interfaces/IMIPS.sol rename to packages/contracts-bedrock/interfaces/cannon/IMIPS.sol index 75f4e50f3ba31..8ad7ccdc21a3b 100644 --- a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS.sol +++ b/packages/contracts-bedrock/interfaces/cannon/IMIPS.sol @@ -1,12 +1,27 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; /// @title IMIPS /// @notice Interface for the MIPS contract. interface IMIPS is ISemver { + struct State { + bytes32 memRoot; + bytes32 preimageKey; + uint32 preimageOffset; + uint32 pc; + uint32 nextPC; + uint32 lo; + uint32 hi; + uint32 heap; + uint8 exitCode; + bool exited; + uint64 step; + uint32[32] registers; + } + error InvalidMemoryProof(); error InvalidRMWInstruction(); diff --git a/packages/contracts-bedrock/interfaces/cannon/IMIPS2.sol b/packages/contracts-bedrock/interfaces/cannon/IMIPS2.sol new file mode 100644 index 0000000000000..bdaf97d77e5b9 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/cannon/IMIPS2.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; + +/// @title IMIPS2 +/// @notice Interface for the MIPS2 contract. +interface IMIPS2 is ISemver { + struct ThreadState { + uint32 threadID; + uint8 exitCode; + bool exited; + uint32 futexAddr; + uint32 futexVal; + uint64 futexTimeoutStep; + uint32 pc; + uint32 nextPC; + uint32 lo; + uint32 hi; + uint32[32] registers; + } + + struct State { + bytes32 memRoot; + bytes32 preimageKey; + uint32 preimageOffset; + uint32 heap; + uint8 llReservationStatus; + uint32 llAddress; + uint32 llOwnerThread; + uint8 exitCode; + bool exited; + uint64 step; + uint64 stepsSinceLastContextSwitch; + uint32 wakeup; + bool traverseRight; + bytes32 leftThreadStack; + bytes32 rightThreadStack; + uint32 nextThreadID; + } + + error InvalidExitedValue(); + error InvalidMemoryProof(); + error InvalidSecondMemoryProof(); + error InvalidRMWInstruction(); + + function oracle() external view returns (IPreimageOracle oracle_); + function step( + bytes memory _stateData, + bytes memory _proof, + bytes32 _localContext + ) + external + returns (bytes32 postState_); + + function __constructor__(IPreimageOracle _oracle) external; +} diff --git a/packages/contracts-bedrock/interfaces/cannon/IPreimageOracle.sol b/packages/contracts-bedrock/interfaces/cannon/IPreimageOracle.sol new file mode 100644 index 0000000000000..956876fd74967 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/cannon/IPreimageOracle.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { LibKeccak } from "@lib-keccak/LibKeccak.sol"; +import { LPPMetaData } from "src/cannon/libraries/CannonTypes.sol"; + +interface IPreimageOracle { + struct Leaf { + bytes input; + uint256 index; + bytes32 stateCommitment; + } + + error ActiveProposal(); + error AlreadyFinalized(); + error AlreadyInitialized(); + error BadProposal(); + error BondTransferFailed(); + error InsufficientBond(); + error InvalidInputSize(); + error InvalidPreimage(); + error InvalidProof(); + error NotEOA(); + error NotInitialized(); + error PartOffsetOOB(); + error PostStateMatches(); + error StatesNotContiguous(); + error TreeSizeOverflow(); + error WrongStartingBlock(); + + function KECCAK_TREE_DEPTH() external view returns (uint256); + function MAX_LEAF_COUNT() external view returns (uint256); + function MIN_BOND_SIZE() external view returns (uint256); + function PRECOMPILE_CALL_RESERVED_GAS() external view returns (uint256); + function addLeavesLPP( + uint256 _uuid, + uint256 _inputStartBlock, + bytes memory _input, + bytes32[] memory _stateCommitments, + bool _finalize + ) + external; + function challengeFirstLPP( + address _claimant, + uint256 _uuid, + Leaf memory _postState, + bytes32[] memory _postStateProof + ) + external; + function challengeLPP( + address _claimant, + uint256 _uuid, + LibKeccak.StateMatrix memory _stateMatrix, + Leaf memory _preState, + bytes32[] memory _preStateProof, + Leaf memory _postState, + bytes32[] memory _postStateProof + ) + external; + function challengePeriod() external view returns (uint256 challengePeriod_); + function getTreeRootLPP(address _owner, uint256 _uuid) external view returns (bytes32 treeRoot_); + function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external payable; + function loadBlobPreimagePart( + uint256 _z, + uint256 _y, + bytes memory _commitment, + bytes memory _proof, + uint256 _partOffset + ) + external; + function loadKeccak256PreimagePart(uint256 _partOffset, bytes memory _preimage) external; + function loadLocalData( + uint256 _ident, + bytes32 _localContext, + bytes32 _word, + uint256 _size, + uint256 _partOffset + ) + external + returns (bytes32 key_); + function loadPrecompilePreimagePart( + uint256 _partOffset, + address _precompile, + uint64 _requiredGas, + bytes memory _input + ) + external; + function loadSha256PreimagePart(uint256 _partOffset, bytes memory _preimage) external; + function minProposalSize() external view returns (uint256 minProposalSize_); + function preimageLengths(bytes32) external view returns (uint256); + function preimagePartOk(bytes32, uint256) external view returns (bool); + function preimageParts(bytes32, uint256) external view returns (bytes32); + function proposalBlocks(address, uint256, uint256) external view returns (uint64); + function proposalBlocksLen(address _claimant, uint256 _uuid) external view returns (uint256 len_); + function proposalBonds(address, uint256) external view returns (uint256); + function proposalBranches(address, uint256, uint256) external view returns (bytes32); + function proposalCount() external view returns (uint256 count_); + function proposalMetadata(address, uint256) external view returns (LPPMetaData); + function proposalParts(address, uint256) external view returns (bytes32); + function proposals(uint256) external view returns (address claimant, uint256 uuid); // nosemgrep: + // sol-style-return-arg-fmt + function readPreimage(bytes32 _key, uint256 _offset) external view returns (bytes32 dat_, uint256 datLen_); + function squeezeLPP( + address _claimant, + uint256 _uuid, + LibKeccak.StateMatrix memory _stateMatrix, + Leaf memory _preState, + bytes32[] memory _preStateProof, + Leaf memory _postState, + bytes32[] memory _postStateProof + ) + external; + function version() external view returns (string memory); + function zeroHashes(uint256) external view returns (bytes32); + + function __constructor__(uint256 _minProposalSize, uint256 _challengePeriod) external; +} diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol b/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol similarity index 76% rename from packages/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol rename to packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol index 4de2bb1deab60..dfb46ad8378f7 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import "src/dispute/lib/Types.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { GameType, Hash, OutputRoot } from "src/dispute/lib/Types.sol"; interface IAnchorStateRegistry { struct StartingAnchorRoot { diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IBigStepper.sol b/packages/contracts-bedrock/interfaces/dispute/IBigStepper.sol similarity index 97% rename from packages/contracts-bedrock/src/dispute/interfaces/IBigStepper.sol rename to packages/contracts-bedrock/interfaces/dispute/IBigStepper.sol index f38e58f9d9d6f..e5a8bd3ec3427 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IBigStepper.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IBigStepper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; /// @title IBigStepper /// @notice Describes a state machine that can perform a single instruction step, provided a prestate and an optional diff --git a/packages/contracts-bedrock/interfaces/dispute/IDelayedWETH.sol b/packages/contracts-bedrock/interfaces/dispute/IDelayedWETH.sol new file mode 100644 index 0000000000000..63ffa49919b63 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/dispute/IDelayedWETH.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +interface IDelayedWETH { + struct WithdrawalRequest { + uint256 amount; + uint256 timestamp; + } + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + event Initialized(uint8 version); + event Unwrap(address indexed src, uint256 wad); + + fallback() external payable; + receive() external payable; + + function config() external view returns (ISuperchainConfig); + function delay() external view returns (uint256); + function hold(address _guy, uint256 _wad) external; + function initialize(address _owner, ISuperchainConfig _config) external; + function owner() external view returns (address); + function recover(uint256 _wad) external; + function transferOwnership(address newOwner) external; // nosemgrep + function renounceOwnership() external; + function unlock(address _guy, uint256 _wad) external; + function withdraw(address _guy, uint256 _wad) external; + function withdrawals(address, address) external view returns (uint256 amount, uint256 timestamp); + function version() external view returns (string memory); + + function withdraw(uint256 _wad) external; + + event Approval(address indexed src, address indexed guy, uint256 wad); + + event Transfer(address indexed src, address indexed dst, uint256 wad); + + event Deposit(address indexed dst, uint256 wad); + + event Withdrawal(address indexed src, uint256 wad); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function decimals() external view returns (uint8); + + function balanceOf(address src) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function deposit() external payable; + + function totalSupply() external view returns (uint256); + + function approve(address guy, uint256 wad) external returns (bool); + + function transfer(address dst, uint256 wad) external returns (bool); + + function transferFrom(address src, address dst, uint256 wad) external returns (bool); + + function __constructor__(uint256 _delay) external; +} diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGame.sol b/packages/contracts-bedrock/interfaces/dispute/IDisputeGame.sol similarity index 83% rename from packages/contracts-bedrock/src/dispute/interfaces/IDisputeGame.sol rename to packages/contracts-bedrock/interfaces/dispute/IDisputeGame.sol index f5a650202d0ce..2f79cc79d3276 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGame.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IDisputeGame.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IInitializable } from "src/dispute/interfaces/IInitializable.sol"; -import "src/dispute/lib/Types.sol"; +import { IInitializable } from "interfaces/dispute/IInitializable.sol"; +import { Timestamp, GameStatus, GameType, Claim, Hash } from "src/dispute/lib/Types.sol"; interface IDisputeGame is IInitializable { event Resolved(GameStatus indexed status); diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGameFactory.sol b/packages/contracts-bedrock/interfaces/dispute/IDisputeGameFactory.sol similarity index 93% rename from packages/contracts-bedrock/src/dispute/interfaces/IDisputeGameFactory.sol rename to packages/contracts-bedrock/interfaces/dispute/IDisputeGameFactory.sol index 0f21d42aa27a1..789d470722915 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IDisputeGameFactory.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IDisputeGameFactory.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import "src/dispute/lib/Types.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { GameId, Timestamp, Claim, Hash, GameType } from "src/dispute/lib/Types.sol"; interface IDisputeGameFactory { struct GameSearchResult { diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IFaultDisputeGame.sol b/packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol similarity index 85% rename from packages/contracts-bedrock/src/dispute/interfaces/IFaultDisputeGame.sol rename to packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol index ec0f86ff709cd..038bb55998f87 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IFaultDisputeGame.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { Types } from "src/libraries/Types.sol"; -import "src/dispute/lib/Types.sol"; +import { GameType, Claim, Position, Clock, Hash, Duration } from "src/dispute/lib/Types.sol"; interface IFaultDisputeGame is IDisputeGame { struct ClaimData { @@ -26,6 +26,19 @@ interface IFaultDisputeGame is IDisputeGame { address counteredBy; } + struct GameConstructorParams { + GameType gameType; + Claim absolutePrestate; + uint256 maxGameDepth; + uint256 splitDepth; + Duration clockExtension; + Duration maxClockDuration; + IBigStepper vm; + IDelayedWETH weth; + IAnchorStateRegistry anchorStateRegistry; + uint256 l2ChainId; + } + error AlreadyInitialized(); error AnchorRootNotFound(); error BlockNumberMatches(); @@ -113,17 +126,5 @@ interface IFaultDisputeGame is IDisputeGame { function vm() external view returns (IBigStepper vm_); function weth() external view returns (IDelayedWETH weth_); - function __constructor__( - GameType _gameType, - Claim _absolutePrestate, - uint256 _maxGameDepth, - uint256 _splitDepth, - Duration _clockExtension, - Duration _maxClockDuration, - IBigStepper _vm, - IDelayedWETH _weth, - IAnchorStateRegistry _anchorStateRegistry, - uint256 _l2ChainId - ) - external; + function __constructor__(GameConstructorParams memory _params) external; } diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IInitializable.sol b/packages/contracts-bedrock/interfaces/dispute/IInitializable.sol similarity index 100% rename from packages/contracts-bedrock/src/dispute/interfaces/IInitializable.sol rename to packages/contracts-bedrock/interfaces/dispute/IInitializable.sol diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IPermissionedDisputeGame.sol b/packages/contracts-bedrock/interfaces/dispute/IPermissionedDisputeGame.sol similarity index 88% rename from packages/contracts-bedrock/src/dispute/interfaces/IPermissionedDisputeGame.sol rename to packages/contracts-bedrock/interfaces/dispute/IPermissionedDisputeGame.sol index 980d3460c048b..c9d26d70a6ca3 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IPermissionedDisputeGame.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IPermissionedDisputeGame.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; -import "src/dispute/lib/Types.sol"; +import { Claim, Position, Clock, Hash, Duration } from "src/dispute/lib/Types.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; interface IPermissionedDisputeGame is IDisputeGame { struct ClaimData { @@ -120,16 +121,7 @@ interface IPermissionedDisputeGame is IDisputeGame { function challenger() external view returns (address challenger_); function __constructor__( - GameType _gameType, - Claim _absolutePrestate, - uint256 _maxGameDepth, - uint256 _splitDepth, - Duration _clockExtension, - Duration _maxClockDuration, - IBigStepper _vm, - IDelayedWETH _weth, - IAnchorStateRegistry _anchorStateRegistry, - uint256 _l2ChainId, + IFaultDisputeGame.GameConstructorParams memory _params, address _proposer, address _challenger ) diff --git a/packages/contracts-bedrock/src/governance/interfaces/IGovernanceToken.sol b/packages/contracts-bedrock/interfaces/governance/IGovernanceToken.sol similarity index 100% rename from packages/contracts-bedrock/src/governance/interfaces/IGovernanceToken.sol rename to packages/contracts-bedrock/interfaces/governance/IGovernanceToken.sol diff --git a/packages/contracts-bedrock/src/governance/interfaces/IMintManager.sol b/packages/contracts-bedrock/interfaces/governance/IMintManager.sol similarity index 91% rename from packages/contracts-bedrock/src/governance/interfaces/IMintManager.sol rename to packages/contracts-bedrock/interfaces/governance/IMintManager.sol index 68399f3336c96..7ed7f1afc403a 100644 --- a/packages/contracts-bedrock/src/governance/interfaces/IMintManager.sol +++ b/packages/contracts-bedrock/interfaces/governance/IMintManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; +import { IGovernanceToken } from "interfaces/governance/IGovernanceToken.sol"; interface IMintManager { event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); diff --git a/packages/contracts-bedrock/src/legacy/interfaces/IAddressManager.sol b/packages/contracts-bedrock/interfaces/legacy/IAddressManager.sol similarity index 87% rename from packages/contracts-bedrock/src/legacy/interfaces/IAddressManager.sol rename to packages/contracts-bedrock/interfaces/legacy/IAddressManager.sol index 0c0004a536754..e925fac5847f9 100644 --- a/packages/contracts-bedrock/src/legacy/interfaces/IAddressManager.sol +++ b/packages/contracts-bedrock/interfaces/legacy/IAddressManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IOwnable } from "src/universal/interfaces/IOwnable.sol"; +import { IOwnable } from "interfaces/universal/IOwnable.sol"; /// @title IAddressManager /// @notice Interface for the AddressManager contract. diff --git a/packages/contracts-bedrock/src/legacy/interfaces/IDeployerWhitelist.sol b/packages/contracts-bedrock/interfaces/legacy/IDeployerWhitelist.sol similarity index 100% rename from packages/contracts-bedrock/src/legacy/interfaces/IDeployerWhitelist.sol rename to packages/contracts-bedrock/interfaces/legacy/IDeployerWhitelist.sol diff --git a/packages/contracts-bedrock/src/legacy/interfaces/IL1BlockNumber.sol b/packages/contracts-bedrock/interfaces/legacy/IL1BlockNumber.sol similarity index 84% rename from packages/contracts-bedrock/src/legacy/interfaces/IL1BlockNumber.sol rename to packages/contracts-bedrock/interfaces/legacy/IL1BlockNumber.sol index 551514632696c..f20770a55d414 100644 --- a/packages/contracts-bedrock/src/legacy/interfaces/IL1BlockNumber.sol +++ b/packages/contracts-bedrock/interfaces/legacy/IL1BlockNumber.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title IL1BlockNumber /// @notice Interface for the L1BlockNumber contract. diff --git a/packages/contracts-bedrock/src/legacy/interfaces/IL1ChugSplashProxy.sol b/packages/contracts-bedrock/interfaces/legacy/IL1ChugSplashProxy.sol similarity index 100% rename from packages/contracts-bedrock/src/legacy/interfaces/IL1ChugSplashProxy.sol rename to packages/contracts-bedrock/interfaces/legacy/IL1ChugSplashProxy.sol diff --git a/packages/contracts-bedrock/src/legacy/interfaces/ILegacyMessagePasser.sol b/packages/contracts-bedrock/interfaces/legacy/ILegacyMessagePasser.sol similarity index 85% rename from packages/contracts-bedrock/src/legacy/interfaces/ILegacyMessagePasser.sol rename to packages/contracts-bedrock/interfaces/legacy/ILegacyMessagePasser.sol index 0eebc30d5878b..d73d57dfa7010 100644 --- a/packages/contracts-bedrock/src/legacy/interfaces/ILegacyMessagePasser.sol +++ b/packages/contracts-bedrock/interfaces/legacy/ILegacyMessagePasser.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title ILegacyMessagePasser /// @notice Interface for the LegacyMessagePasser contract. diff --git a/packages/contracts-bedrock/src/legacy/interfaces/IResolvedDelegateProxy.sol b/packages/contracts-bedrock/interfaces/legacy/IResolvedDelegateProxy.sol similarity index 82% rename from packages/contracts-bedrock/src/legacy/interfaces/IResolvedDelegateProxy.sol rename to packages/contracts-bedrock/interfaces/legacy/IResolvedDelegateProxy.sol index b3201ff0b1c7d..a677d5b0a478c 100644 --- a/packages/contracts-bedrock/src/legacy/interfaces/IResolvedDelegateProxy.sol +++ b/packages/contracts-bedrock/interfaces/legacy/IResolvedDelegateProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; /// @title IResolvedDelegateProxy /// @notice Interface for the ResolvedDelegateProxy contract. diff --git a/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol b/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol new file mode 100644 index 0000000000000..a5c0e33130279 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { GameType, Timestamp } from "src/dispute/lib/Types.sol"; +import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; + +interface IDeputyGuardianModule is ISemver { + error ExecutionFailed(string); + error Unauthorized(); + + event Paused(string identifier); + event Unpaused(); + event DisputeGameBlacklisted(IDisputeGame indexed game); + event RespectedGameTypeSet(GameType indexed gameType, Timestamp indexed updatedAt); + + function version() external view returns (string memory); + function __constructor__(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) external; + function safe() external view returns (Safe safe_); + function superchainConfig() external view returns (ISuperchainConfig superchainConfig_); + function deputyGuardian() external view returns (address deputyGuardian_); + function pause() external; + function unpause() external; + function setAnchorState(IAnchorStateRegistry _registry, IFaultDisputeGame _game) external; + function blacklistDisputeGame(IOptimismPortal2 _portal, IDisputeGame _game) external; + function setRespectedGameType(IOptimismPortal2 _portal, GameType _gameType) external; +} diff --git a/packages/contracts-bedrock/interfaces/safe/ILivenessGuard.sol b/packages/contracts-bedrock/interfaces/safe/ILivenessGuard.sol new file mode 100644 index 0000000000000..db6761096d12d --- /dev/null +++ b/packages/contracts-bedrock/interfaces/safe/ILivenessGuard.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; +import { Enum } from "safe-contracts/common/Enum.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +interface ILivenessGuard is ISemver { + event OwnerRecorded(address owner); + + function lastLive(address) external view returns (uint256); + function version() external view returns (string memory); + function __constructor__(Safe _safe) external; + + function safe() external view returns (Safe safe_); + function checkTransaction( + address _to, + uint256 _value, + bytes memory _data, + Enum.Operation _operation, + uint256 _safeTxGas, + uint256 _baseGas, + uint256 _gasPrice, + address _gasToken, + address payable _refundReceiver, + bytes memory _signatures, + address _msgSender + ) + external; + function checkAfterExecution(bytes32, bool) external; + function showLiveness() external; +} diff --git a/packages/contracts-bedrock/interfaces/safe/ILivenessModule.sol b/packages/contracts-bedrock/interfaces/safe/ILivenessModule.sol new file mode 100644 index 0000000000000..ad5088fcf2bb5 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/safe/ILivenessModule.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; +import { LivenessGuard } from "src/safe/LivenessGuard.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; + +interface ILivenessModule is ISemver { + error OwnerRemovalFailed(string); + + event RemovedOwner(address indexed owner); + event OwnershipTransferredToFallback(); + + function ownershipTransferredToFallback() external view returns (bool); + function version() external view returns (string memory); + function __constructor__( + Safe _safe, + LivenessGuard _livenessGuard, + uint256 _livenessInterval, + uint256 _minOwners, + uint256 _thresholdPercentage, + address _fallbackOwner + ) + external; + function getRequiredThreshold(uint256 _numOwners) external view returns (uint256 threshold_); + function safe() external view returns (Safe safe_); + function livenessGuard() external view returns (LivenessGuard livenessGuard_); + function livenessInterval() external view returns (uint256 livenessInterval_); + function minOwners() external view returns (uint256 minOwners_); + function thresholdPercentage() external view returns (uint256 thresholdPercentage_); + function fallbackOwner() external view returns (address fallbackOwner_); + function canRemove(address _owner) external view returns (bool canRemove_); + function removeOwners(address[] memory _previousOwners, address[] memory _ownersToRemove) external; +} diff --git a/packages/contracts-bedrock/src/universal/interfaces/ICrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/ICrossDomainMessenger.sol rename to packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol diff --git a/packages/contracts-bedrock/src/universal/interfaces/IEIP712.sol b/packages/contracts-bedrock/interfaces/universal/IEIP712.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/IEIP712.sol rename to packages/contracts-bedrock/interfaces/universal/IEIP712.sol diff --git a/packages/contracts-bedrock/src/universal/interfaces/IERC721Bridge.sol b/packages/contracts-bedrock/interfaces/universal/IERC721Bridge.sol similarity index 93% rename from packages/contracts-bedrock/src/universal/interfaces/IERC721Bridge.sol rename to packages/contracts-bedrock/interfaces/universal/IERC721Bridge.sol index 3c97958c1033c..79497fa6383c0 100644 --- a/packages/contracts-bedrock/src/universal/interfaces/IERC721Bridge.sol +++ b/packages/contracts-bedrock/interfaces/universal/IERC721Bridge.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; interface IERC721Bridge { event ERC721BridgeFinalized( diff --git a/packages/contracts-bedrock/interfaces/universal/ILegacyMintableERC20.sol b/packages/contracts-bedrock/interfaces/universal/ILegacyMintableERC20.sol new file mode 100644 index 0000000000000..ad518b8b4d774 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/universal/ILegacyMintableERC20.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +/// @custom:legacy +/// @title ILegacyMintableERC20 +/// @notice This interface was available on the legacy L2StandardERC20 contract. +/// It remains available on the OptimismMintableERC20 contract for +/// backwards compatibility. + +interface ILegacyMintableERC20 is IERC165 { + function l1Token() external view returns (address); + + function mint(address _to, uint256 _amount) external; + + function burn(address _from, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC20.sol b/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20.sol similarity index 58% rename from packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC20.sol rename to packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20.sol index b261902c72ee8..caf54b8073149 100644 --- a/packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC20.sol +++ b/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20.sol @@ -16,16 +16,3 @@ interface IOptimismMintableERC20 is IERC165 { function burn(address _from, uint256 _amount) external; } - -/// @custom:legacy -/// @title ILegacyMintableERC20 -/// @notice This interface was available on the legacy L2StandardERC20 contract. -/// It remains available on the OptimismMintableERC20 contract for -/// backwards compatibility. -interface ILegacyMintableERC20 is IERC165 { - function l1Token() external view returns (address); - - function mint(address _to, uint256 _amount) external; - - function burn(address _from, uint256 _amount) external; -} diff --git a/packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC20Factory.sol b/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20Factory.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC20Factory.sol rename to packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20Factory.sol diff --git a/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC721.sol b/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC721.sol new file mode 100644 index 0000000000000..7d745ad8436e6 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC721.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title IOptimismMintableERC721 +/// @notice Interface for contracts that are compatible with the OptimismMintableERC721 standard. +/// Tokens that follow this standard can be easily transferred across the ERC721 bridge. +interface IOptimismMintableERC721 { + function __constructor__( + address _bridge, + uint256 _remoteChainId, + address _remoteToken, + string memory _name, + string memory _symbol + ) + external; + + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event Burn(address indexed account, uint256 tokenId); + event Mint(address indexed account, uint256 tokenId); + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + function totalSupply() external view returns (uint256); + + function approve(address to, uint256 tokenId) external; + + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function symbol() external view returns (string memory); + + function tokenByIndex(uint256 index) external view returns (uint256); + + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256); + + function transferFrom(address from, address to, uint256 tokenId) external; + + function balanceOf(address owner) external view returns (uint256); + + function baseTokenURI() external view returns (string memory); + + function getApproved(uint256 tokenId) external view returns (address); + + function name() external view returns (string memory); + + function ownerOf(uint256 tokenId) external view returns (address); + + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) external; + + function setApprovalForAll(address operator, bool approved) external; + + function supportsInterface(bytes4 _interfaceId) external view returns (bool); + + function tokenURI(uint256 tokenId) external view returns (string memory); + + function version() external view returns (string memory); + + function safeMint(address _to, uint256 _tokenId) external; + + function burn(address _from, uint256 _tokenId) external; + + function REMOTE_CHAIN_ID() external view returns (uint256); + + function REMOTE_TOKEN() external view returns (address); + + function BRIDGE() external view returns (address); + + function remoteChainId() external view returns (uint256); + + function remoteToken() external view returns (address); + + function bridge() external view returns (address); +} diff --git a/packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC721Factory.sol b/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC721Factory.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC721Factory.sol rename to packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC721Factory.sol diff --git a/packages/contracts-bedrock/src/universal/interfaces/IOwnable.sol b/packages/contracts-bedrock/interfaces/universal/IOwnable.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/IOwnable.sol rename to packages/contracts-bedrock/interfaces/universal/IOwnable.sol diff --git a/packages/contracts-bedrock/src/universal/interfaces/IProxy.sol b/packages/contracts-bedrock/interfaces/universal/IProxy.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/IProxy.sol rename to packages/contracts-bedrock/interfaces/universal/IProxy.sol diff --git a/packages/contracts-bedrock/src/universal/interfaces/IProxyAdmin.sol b/packages/contracts-bedrock/interfaces/universal/IProxyAdmin.sol similarity index 95% rename from packages/contracts-bedrock/src/universal/interfaces/IProxyAdmin.sol rename to packages/contracts-bedrock/interfaces/universal/IProxyAdmin.sol index b35947e6cd78e..09688257b925c 100644 --- a/packages/contracts-bedrock/src/universal/interfaces/IProxyAdmin.sol +++ b/packages/contracts-bedrock/interfaces/universal/IProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; interface IProxyAdmin { enum ProxyType { diff --git a/packages/contracts-bedrock/src/universal/interfaces/ISemver.sol b/packages/contracts-bedrock/interfaces/universal/ISemver.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/ISemver.sol rename to packages/contracts-bedrock/interfaces/universal/ISemver.sol diff --git a/packages/contracts-bedrock/src/universal/interfaces/IStandardBridge.sol b/packages/contracts-bedrock/interfaces/universal/IStandardBridge.sol similarity index 95% rename from packages/contracts-bedrock/src/universal/interfaces/IStandardBridge.sol rename to packages/contracts-bedrock/interfaces/universal/IStandardBridge.sol index 406a172c07377..a9c45017204dd 100644 --- a/packages/contracts-bedrock/src/universal/interfaces/IStandardBridge.sol +++ b/packages/contracts-bedrock/interfaces/universal/IStandardBridge.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; interface IStandardBridge { event ERC20BridgeFinalized( diff --git a/packages/contracts-bedrock/src/universal/interfaces/IStaticERC1967Proxy.sol b/packages/contracts-bedrock/interfaces/universal/IStaticERC1967Proxy.sol similarity index 100% rename from packages/contracts-bedrock/src/universal/interfaces/IStaticERC1967Proxy.sol rename to packages/contracts-bedrock/interfaces/universal/IStaticERC1967Proxy.sol diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol b/packages/contracts-bedrock/interfaces/universal/IWETH98.sol similarity index 60% rename from packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol rename to packages/contracts-bedrock/interfaces/universal/IWETH98.sol index 1204c328fc89c..c7a77b7a36cd5 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol +++ b/packages/contracts-bedrock/interfaces/universal/IWETH98.sol @@ -1,35 +1,41 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -interface ISuperchainWETH { - error NotCustomGasToken(); - error Unauthorized(); +/// @title IWETH +/// @notice Interface for WETH9. +interface IWETH98 { + receive() external payable; + fallback() external payable; event Approval(address indexed src, address indexed guy, uint256 wad); - event Deposit(address indexed dst, uint256 wad); - event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source); - event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination); + event Transfer(address indexed src, address indexed dst, uint256 wad); + + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); - fallback() external payable; + function name() external view returns (string memory); - receive() external payable; + function symbol() external view returns (string memory); - function allowance(address, address) external view returns (uint256); - function approve(address guy, uint256 wad) external returns (bool); - function balanceOf(address) external view returns (uint256); function decimals() external view returns (uint8); + + function balanceOf(address src) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + function deposit() external payable; - function name() external view returns (string memory); - function relayERC20(address from, address dst, uint256 wad) external; - function sendERC20(address dst, uint256 wad, uint256 chainId) external; - function symbol() external view returns (string memory); + + function withdraw(uint256 wad) external; + function totalSupply() external view returns (uint256); + + function approve(address guy, uint256 wad) external returns (bool); + function transfer(address dst, uint256 wad) external returns (bool); + function transferFrom(address src, address dst, uint256 wad) external returns (bool); - function version() external view returns (string memory); - function withdraw(uint256 wad) external; function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/vendor/interfaces/IERC20Solady.sol b/packages/contracts-bedrock/interfaces/vendor/IERC20Solady.sol similarity index 96% rename from packages/contracts-bedrock/src/vendor/interfaces/IERC20Solady.sol rename to packages/contracts-bedrock/interfaces/vendor/IERC20Solady.sol index 1e696ad23ac31..b05b906eec970 100644 --- a/packages/contracts-bedrock/src/vendor/interfaces/IERC20Solady.sol +++ b/packages/contracts-bedrock/interfaces/vendor/IERC20Solady.sol @@ -23,6 +23,9 @@ interface IERC20Solady { /// @dev The permit has expired. error PermitExpired(); + /// @dev The allowance of Permit2 is fixed at infinity. + error Permit2AllowanceIsFixedAtInfinity(); + /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 amount); diff --git a/packages/contracts-bedrock/src/vendor/interfaces/IGelatoTreasury.sol b/packages/contracts-bedrock/interfaces/vendor/IGelatoTreasury.sol similarity index 100% rename from packages/contracts-bedrock/src/vendor/interfaces/IGelatoTreasury.sol rename to packages/contracts-bedrock/interfaces/vendor/IGelatoTreasury.sol diff --git a/packages/contracts-bedrock/interfaces/vendor/asterisc/IRISCV.sol b/packages/contracts-bedrock/interfaces/vendor/asterisc/IRISCV.sol new file mode 100644 index 0000000000000..d69a22dfb0c45 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/vendor/asterisc/IRISCV.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; + +/// @title IRISCV +/// @notice Interface for the RISCV contract. +interface IRISCV is ISemver { + function oracle() external view returns (IPreimageOracle); + function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); + + function __constructor__(IPreimageOracle _oracle) external; +} diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 06228b3630d86..6a34cc6998d32 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -15,21 +15,23 @@ dep-status: # BUILD # ######################################################## -# Checks that the correct version of Foundry is installed. -prebuild: - ./scripts/checks/check-foundry-install.sh - # Core forge build command forge-build: forge build # Builds the contracts. -build: prebuild lint-fix-no-fail forge-build interfaces-check-no-build +build: lint-fix-no-fail forge-build interfaces-check-no-build # Builds the go-ffi tool for contract tests. -build-go-ffi: +build-go-ffi-default: cd ./scripts/go-ffi && go build +# Builds the go-ffi tool for MIPS64 contract tests. +build-go-ffi-cannon64: + cd ./scripts/go-ffi && go build -tags=cannon64 -o ./go-ffi-cannon64 + +build-go-ffi: build-go-ffi-default build-go-ffi-cannon64 + # Cleans build artifacts and deployments. clean: rm -rf ./artifacts ./forge-artifacts ./cache ./scripts/go-ffi/go-ffi ./deployments/hardhat/* @@ -40,8 +42,8 @@ clean: ######################################################## # Runs standard contract tests. -test: build-go-ffi - forge test +test *ARGS: build-go-ffi + forge test {{ARGS}} # Runs standard contract tests with rerun flag. test-rerun: build-go-ffi @@ -56,11 +58,11 @@ test-kontrol-no-build: # Runs contract coverage. coverage: build-go-ffi - forge coverage || (bash -c "forge coverage 2>&1 | grep -q 'Stack too deep' && echo -e '\\033[1;33mWARNING\\033[0m: Coverage failed with stack too deep, so overriding and exiting successfully' && exit 0 || exit 1") + forge coverage # Runs contract coverage with lcov. coverage-lcov: build-go-ffi - forge coverage --report lcov || (bash -c "forge coverage --report lcov 2>&1 | grep -q 'Stack too deep' && echo -e '\\033[1;33mWARNING\\033[0m: Coverage failed with stack too deep, so overriding and exiting successfully' && exit 0 || exit 1") + forge coverage --report lcov ######################################################## @@ -82,7 +84,7 @@ deploy: # Generates a gas snapshot without building. gas-snapshot-no-build: - forge snapshot --match-contract GasBenchMark + forge snapshot --match-contract GasBenchMark --snap snapshots/.gas-snapshot # Generates a gas snapshot. gas-snapshot: build-go-ffi gas-snapshot-no-build @@ -102,9 +104,9 @@ kontrol-summary-full: kontrol-summary kontrol-summary-fp snapshots-abi-storage: go run ./scripts/autogen/generate-snapshots . -# Updates the semver-lock.json file. +# Updates the snapshots/semver-lock.json file. semver-lock: - forge script scripts/autogen/SemverLock.s.sol + go run scripts/autogen/generate-semver-lock/main.go # Generates core snapshots without building contracts. Currently just an alias for # snapshots-abi-storage because we no longer run Kontrol snapshots here. Run @@ -121,15 +123,11 @@ snapshots: build snapshots-no-build # Checks that the gas snapshot is up to date without building. gas-snapshot-check-no-build: - forge snapshot --match-contract GasBenchMark --check + forge snapshot --match-contract GasBenchMark --snap snapshots/.gas-snapshot --check # Checks that the gas snapshot is up to date. gas-snapshot-check: build-go-ffi gas-snapshot-check-no-build -# Checks that the Kontrol deployment script has not changed. -kontrol-deployment-check: - ./scripts/checks/check-kontrol-deployment.sh - # Checks if the snapshots are up to date without building. snapshots-check-no-build: ./scripts/checks/check-snapshots.sh --no-build @@ -159,17 +157,16 @@ semver-diff-check-no-build: # Checks that any contracts with a modified semver lock also have a modified semver version. semver-diff-check: build semver-diff-check-no-build -# Checks that semver natspec is equal to the actual semver version. -# Does not build contracts. -semver-natspec-check-no-build: - go run ./scripts/checks/semver-natspec +# Checks that the semgrep tests are valid. +semgrep-test-validity-check: + forge fmt ../../.semgrep/tests/sol-rules.t.sol --check -# Checks that semver natspec is equal to the actual semver version. -semver-natspec-check: build semver-natspec-check-no-build +# Checks that forge test names are correctly formatted. Does not build contracts. +lint-forge-tests-check-no-build: + go run ./scripts/checks/test-names # Checks that forge test names are correctly formatted. -lint-forge-tests-check: - go run ./scripts/checks/names +lint-forge-tests-check: build lint-forge-tests-check-no-build # Checks that contracts are properly linted. lint-check: @@ -193,19 +190,32 @@ validate-spacers-no-build: # Checks that spacer variables are correctly inserted. validate-spacers: build validate-spacers-no-build -# TODO: Also run lint-forge-tests-check but we need to fix the test names first. +# Checks that the Kontrol summary dummy files have not been modified. +# If you have changed the summary files deliberately, update the hashes in the script. +# Use `openssl dgst -sha256` to generate the hash for a file. +check-kontrol-summaries-unchanged: + ./scripts/checks/check-kontrol-summaries-unchanged.sh + +# Runs semgrep on the contracts. +semgrep: + cd ../../ && semgrep scan --config .semgrep/rules/ ./packages/contracts-bedrock + +# Runs semgrep tests. +semgrep-test: + cd ../../ && semgrep scan --test --config .semgrep/rules/ .semgrep/tests/ + # Runs all checks. check: @just gas-snapshot-check-no-build \ + semgrep-test-validity-check \ unused-imports-check-no-build \ - kontrol-deployment-check \ snapshots-check-no-build \ lint-check \ semver-diff-check-no-build \ - semver-natspec-check-no-build \ validate-deploy-configs \ validate-spacers-no-build \ - interfaces-check-no-build + interfaces-check-no-build \ + lint-forge-tests-check-no-build ######################################################## # DEV TOOLS # diff --git a/packages/contracts-bedrock/lib/lib-keccak b/packages/contracts-bedrock/lib/lib-keccak index 0115edbbc60b5..3b1e7bbb4cc23 160000 --- a/packages/contracts-bedrock/lib/lib-keccak +++ b/packages/contracts-bedrock/lib/lib-keccak @@ -1 +1 @@ -Subproject commit 0115edbbc60b5f702392caafc3a142061e6142fa +Subproject commit 3b1e7bbb4cc23e9228097cfebe42aedaf3b8f2b9 diff --git a/packages/contracts-bedrock/lib/solady-v0.0.245 b/packages/contracts-bedrock/lib/solady-v0.0.245 new file mode 160000 index 0000000000000..e0ef35adb0ccd --- /dev/null +++ b/packages/contracts-bedrock/lib/solady-v0.0.245 @@ -0,0 +1 @@ +Subproject commit e0ef35adb0ccd1032794731a995cb599bba7b537 diff --git a/packages/contracts-bedrock/INTERFACES.md b/packages/contracts-bedrock/meta/INTERFACES.md similarity index 100% rename from packages/contracts-bedrock/INTERFACES.md rename to packages/contracts-bedrock/meta/INTERFACES.md diff --git a/packages/contracts-bedrock/LICENSE b/packages/contracts-bedrock/meta/LICENSE similarity index 100% rename from packages/contracts-bedrock/LICENSE rename to packages/contracts-bedrock/meta/LICENSE diff --git a/packages/contracts-bedrock/meta/POLICY.md b/packages/contracts-bedrock/meta/POLICY.md new file mode 100644 index 0000000000000..e2759f4eff129 --- /dev/null +++ b/packages/contracts-bedrock/meta/POLICY.md @@ -0,0 +1,36 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Policy](#policy) + - [Contributing](#contributing) + - [Versioning Policy](#versioning-policy) + - [Upgrade Policy](#upgrade-policy) + - [Style Guide](#style-guide) + - [Revert Data](#revert-data) + + + +# Policy + +This document outlines upgrade policies regarding the OP Stack codebase. + +## Contributing + +For any policies on contributing, please see [CONTRIBUTING](./CONTRIBUTING.md) + +## Versioning Policy + +For our versioning policy, please see our policy on [VERSIONING](./VERSIONING.md) + +## Upgrade Policy + +For the solidity upgrade policy, please see our doc on [SOLIDITY UPGRADES](./SOLIDITY_UPGRADES.md) + +## Style Guide + +For an indepth review of the code style used in the OP Stack contracts, please see our [STYLE GUIDE](./STYLE_GUIDE.md) + +## Revert Data + +Revert data may be changed in the future, and is not a reliable interface for external consumers. Contracts should not depend on specific revert data returned by OP Stack contracts, which can be changed during any future OP Stack contract upgrades. Revert data includes both custom errors returned by contracts, as a well as revert strings. diff --git a/packages/contracts-bedrock/SOLIDITY_UPGRADES.md b/packages/contracts-bedrock/meta/SOLIDITY_UPGRADES.md similarity index 100% rename from packages/contracts-bedrock/SOLIDITY_UPGRADES.md rename to packages/contracts-bedrock/meta/SOLIDITY_UPGRADES.md diff --git a/packages/contracts-bedrock/STYLE_GUIDE.md b/packages/contracts-bedrock/meta/STYLE_GUIDE.md similarity index 98% rename from packages/contracts-bedrock/STYLE_GUIDE.md rename to packages/contracts-bedrock/meta/STYLE_GUIDE.md index 1d8b84818b9bf..00af13d5a5b49 100644 --- a/packages/contracts-bedrock/STYLE_GUIDE.md +++ b/packages/contracts-bedrock/meta/STYLE_GUIDE.md @@ -96,8 +96,8 @@ Spacers MUST be `private`. All contracts should be assumed to live behind proxies (except in certain special circumstances). This means that new contracts MUST be built under the assumption of upgradeability. -We use a minimal [`Proxy`](./src/universal/Proxy.sol) contract designed to be owned by a -corresponding [`ProxyAdmin`](./src/universal/ProxyAdmin.sol) which follow the interfaces +We use a minimal [`Proxy`](../src/universal/Proxy.sol) contract designed to be owned by a +corresponding [`ProxyAdmin`](../src/universal/ProxyAdmin.sol) which follow the interfaces of OpenZeppelin's `Proxy` and `ProxyAdmin` contracts, respectively. Unless explicitly discussed otherwise, you MUST include the following basic upgradeability diff --git a/packages/contracts-bedrock/VERSIONING.md b/packages/contracts-bedrock/meta/VERSIONING.md similarity index 91% rename from packages/contracts-bedrock/VERSIONING.md rename to packages/contracts-bedrock/meta/VERSIONING.md index fefc2211b3e58..cb2b6b6921248 100644 --- a/packages/contracts-bedrock/VERSIONING.md +++ b/packages/contracts-bedrock/meta/VERSIONING.md @@ -11,7 +11,6 @@ There are five parts to the versioning and release process: - [Release Process](#release-process): The process for deploying contracts, creating a governance proposal, and the required associated releases. - [Additional Release Candidates](#additional-release-candidates): How to handle additional release candidates after an initial `op-contracts/vX.Y.Z-rc.1` release. - [Merging Back to Develop After Governance Approval](#merging-back-to-develop-after-governance-approval): Explains how to choose the resulting contract versions when merging back into `develop`. -- [Changelog](#changelog): A CHANGELOG for contract releases is maintained. > [!NOTE] > The rules described in this document must be enforced manually. @@ -119,14 +118,3 @@ Now there are two scenarios for the PR that merges the release branch back into - In practice, this one unlikely to occur when using inheritance for feature development, as specified in [Smart Contract Feature Development](https://github.com/ethereum-optimism/design-docs/blob/main/smart-contract-feature-development.md) architecture. It's more likely that (1) is the case, and we merge the version change into the base contract. This flow also provides a dedicated branch for each release, making it easy to deploy a patch or bug fix, regardless of other changes that may have occurred on develop since the release. - -## Changelog - -Lastly, a CHANGELOG for contract releases must be maintained: - -- Each upcoming release will have a tracking issue that documents the new versions of each contract that will be included in the release, along with links to the PRs that made the changes. -- Every contracts PR must have an accompanying changelog entry in a tracking issue once it is merged. -- Tracking issue titles should be named based on the expected Upgrade number they will go to governance with, e.g. "op-contracts changelog: Upgrade 9". - - See [ethereum-optimism/optimism#10592](https://github.com/ethereum-optimism/optimism/issues/10592) for an example of what this tracking issue should look like. - - We do not include a version number in the issue because it may be hard to predict the final version number of a release until all PRs are merged. - - Using upgrade numbers also acts as a forcing function to ensure upgrade sequencing and the governance process is accounted for early in the development process. diff --git a/packages/contracts-bedrock/safe.json b/packages/contracts-bedrock/safe.json deleted file mode 100644 index 1168ce3d51500..0000000000000 --- a/packages/contracts-bedrock/safe.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "version": "1.0", - "chainId": "11155111", - "createdAt": 1718216278, - "meta": { - "name": "Transactions Batch", - "description": "", - "txBuilderVersion": "1.16.5", - "createdFromSafeAddress": "0x422c32c10b16c442426Eb7E93b07f7B521294565", - "createdFromOwnerAddress": "", - "checksum": "0xc2d69412777b9d9ba16c95d12f3aca13820b37a8db5f9d436f63058c48994cde" - }, - "transactions": [ - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000176f7065726174696f6e735f73657175656e6365725f7632000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0xee8ca3b568c76a8fa53bf566fc0934edddf25deba2199a53772b6c3a05ce20f5b8d220f8", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000166f7065726174696f6e735f70726f706f7365725f763200000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0xee8ca3b5c790be67575b95a6b0c9f618fbdce9b72b6e7202233f0afe087bb641e3d1f0e8", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000146f7065726174696f6e735f67656c61746f5f7632000000000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0xee8ca3b5f666fe8af1ff0032744dcf71fbbeada5e47079f37425a8a0069350bbb508c3db", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000186f7065726174696f6e735f6368616c6c656e6765725f76310000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0xee8ca3b53fa0ce0493ae4a3e182ec7ed727076c63bf2d7761a43e328e118b2afdf86afc3", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0xe551cdaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000176f7065726174696f6e735f73657175656e6365725f763400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e10000000000000000000000000af8c77cfeb57620c4d9dcc81df75a1f0da7064af00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008f23bb38f531600e5d8fddaaec41f13fab46e98c00000000000000000000000000000000000000000000003635c9adc5dea00000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008f23bb38f531600e5d8fddaaec41f13fab46e98c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d63100000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0x3323b467000000000000000000000000a0ff2a54adc3fb33c44a141e67d194cf249258cb000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006467148cd2000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000176f7065726174696f6e735f73657175656e6365725f763400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000176f7065726174696f6e735f73657175656e6365725f763400000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057e40", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000176f7065726174696f6e735f73657175656e6365725f7634000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0xe551cdaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000166f7065726174696f6e735f70726f706f7365725f76330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015180000000000000000000000000af8c77cfeb57620c4d9dcc81df75a1f0da7064af00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000049277ee36a024120ee218127354c4a3591dc90a90000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000049277ee36a024120ee218127354c4a3591dc90a90000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000001158e460913d00000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0x3323b467000000000000000000000000a0ff2a54adc3fb33c44a141e67d194cf249258cb000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006467148cd2000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000166f7065726174696f6e735f70726f706f7365725f76330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000166f7065726174696f6e735f70726f706f7365725f76330000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083d600", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000166f7065726174696f6e735f70726f706f7365725f763300000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0xe551cdaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000186f7065726174696f6e735f6368616c6c656e6765725f7632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e10000000000000000000000000af8c77cfeb57620c4d9dcc81df75a1f0da7064af00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffb026f67da0869eb3abb090cb7f015ce0925cdf00000000000000000000000000000000000000000000003635c9adc5dea000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000049277ee36a024120ee218127354c4a3591dc90a900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d63100000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0x3323b467000000000000000000000000a0ff2a54adc3fb33c44a141e67d194cf249258cb000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006467148cd2000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000186f7065726174696f6e735f6368616c6c656e6765725f7632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000186f7065726174696f6e735f6368616c6c656e6765725f7632000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057e40", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000186f7065726174696f6e735f6368616c6c656e6765725f76320000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0xe551cdaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000146f7065726174696f6e735f67656c61746f5f7633000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000ff11afac4146a0babf7f9f042a22c8053a5467400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007506c12a824d73d9b08564d5afc22c949434755e0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000cd438409d5cac9d2e076ac7bd0bf2377e99bb6e4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007506c12a824d73d9b08564d5afc22c949434755e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002100000000000000000000000000cd438409d5cac9d2e076ac7bd0bf2377e99bb6e400000000000000000000000000000000000000000000000000000000000000", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0x2a6c106ae13b558bb9e2ec64bd2f1f7beff3a5e0", - "value": "0", - "data": "0x3323b467000000000000000000000000a0ff2a54adc3fb33c44a141e67d194cf249258cb000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006467148cd2000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000146f7065726174696f6e735f67656c61746f5f763300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000146f7065726174696f6e735f67656c61746f5f763300000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083d600", - "contractMethod": null, - "contractInputsValues": null - }, - { - "to": "0xa0ff2a54adc3fb33c44a141e67d194cf249258cb", - "value": "0", - "data": "0x9bc94d010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000146f7065726174696f6e735f67656c61746f5f7633000000000000000000000000", - "contractMethod": null, - "contractInputsValues": null - } - ] -} \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index 0069bfa30f94e..72071ef3228ea 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -7,10 +7,8 @@ import { Vm } from "forge-std/Vm.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Config } from "scripts/libraries/Config.sol"; import { StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; -import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { LibString } from "@solady/utils/LibString.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; import { Process } from "scripts/libraries/Process.sol"; /// @notice Represents a deployment. Is serialized to JSON as a key/value @@ -69,11 +67,7 @@ abstract contract Artifacts { /// as well as the JSON files that contain addresses in the `superchain-registry` /// repo. The JSON key is the name of the contract and the value is an address. function _loadAddresses(string memory _path) internal { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat("jq -cr < ", _path); - string memory json = string(Process.run(commands)); + string memory json = Process.bash(string.concat("jq -cr < ", _path)); string[] memory keys = vm.parseJsonKeys(json, ""); for (uint256 i; i < keys.length; i++) { string memory key = keys[i]; @@ -111,6 +105,8 @@ abstract contract Artifacts { bytes32 digest = keccak256(bytes(_name)); if (digest == keccak256(bytes("L2CrossDomainMessenger"))) { return payable(Predeploys.L2_CROSS_DOMAIN_MESSENGER); + } else if (digest == keccak256(bytes("SoulGasToken"))) { + return payable(Predeploys.SOUL_GAS_TOKEN); } else if (digest == keccak256(bytes("L2ToL1MessagePasser"))) { return payable(Predeploys.L2_TO_L1_MESSAGE_PASSER); } else if (digest == keccak256(bytes("L2StandardBridge"))) { @@ -157,6 +153,8 @@ abstract contract Artifacts { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + } else if (digest == keccak256(bytes("SuperchainTokenBridge"))) { + return payable(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); } return payable(address(0)); } @@ -183,6 +181,7 @@ abstract contract Artifacts { /// @param _name The name of the deployment. /// @param _deployed The address of the deployment. function save(string memory _name, address _deployed) public { + console.log("Saving %s: %s", _name, _deployed); if (bytes(_name).length == 0) { revert InvalidDeployment("EmptyName"); } @@ -190,7 +189,6 @@ abstract contract Artifacts { revert InvalidDeployment("AlreadyExists"); } - console.log("Saving %s: %s", _name, _deployed); Deployment memory deployment = Deployment({ name: _name, addr: payable(_deployed) }); _namedDeployments[_name] = deployment; _newDeployments.push(deployment); @@ -216,27 +214,14 @@ abstract contract Artifacts { /// @notice Returns the value of the internal `_initialized` storage slot for a given contract. function loadInitializedSlot(string memory _contractName) public returns (uint8 initialized_) { - // FaultDisputeGame and PermissionedDisputeGame are initializable but cannot be loaded with - // this function yet because they are not properly labeled in the deploy script. - // TODO: Remove this restriction once the deploy script is fixed. - if (LibString.eq(_contractName, "FaultDisputeGame") || LibString.eq(_contractName, "PermissionedDisputeGame")) { - revert UnsupportedInitializableContract(_contractName); - } + address contractAddress = mustGetAddress(_contractName); - address contractAddress; - // Check if the contract name ends with `Proxy` and, if so, get the implementation address + // Check if the contract name ends with `Proxy` and, if so override the contract name which is used to + // retrieve the storage layout. if (LibString.endsWith(_contractName, "Proxy")) { - contractAddress = EIP1967Helper.getImplementation(getAddress(_contractName)); _contractName = LibString.slice(_contractName, 0, bytes(_contractName).length - 5); - // If the EIP1967 implementation address is 0, we try to get the implementation address from legacy - // AddressManager, which would work if the proxy is ResolvedDelegateProxy like L1CrossDomainMessengerProxy. - if (contractAddress == address(0)) { - contractAddress = - IAddressManager(mustGetAddress("AddressManager")).getAddress(string.concat("OVM_", _contractName)); - } - } else { - contractAddress = mustGetAddress(_contractName); } + StorageSlot memory slot = ForgeArtifacts.getInitializedSlot(_contractName); bytes32 slotVal = vm.load(contractAddress, bytes32(vm.parseUint(slot.slot))); initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF); diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index dd685d546ed71..5e7a8dae593da 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -18,24 +18,21 @@ import { Preinstalls } from "src/libraries/Preinstalls.sol"; import { Types } from "src/libraries/Types.sol"; // Interfaces -import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol"; -import { IBaseFeeVault } from "src/L2/interfaces/IBaseFeeVault.sol"; -import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol"; -import { IOptimismSuperchainERC20Beacon } from "src/L2/interfaces/IOptimismSuperchainERC20Beacon.sol"; -import { IOptimismMintableERC721Factory } from "src/universal/interfaces/IOptimismMintableERC721Factory.sol"; -import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; -import { IL2StandardBridge } from "src/L2/interfaces/IL2StandardBridge.sol"; -import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; -import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { IL2CrossDomainMessenger } from "src/L2/interfaces/IL2CrossDomainMessenger.sol"; -import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; - -interface IInitializable { - function initialize(address _addr) external; -} +import { ISequencerFeeVault } from "interfaces/L2/ISequencerFeeVault.sol"; +import { IBaseFeeVault } from "interfaces/L2/IBaseFeeVault.sol"; +import { IL1FeeVault } from "interfaces/L2/IL1FeeVault.sol"; +import { IOptimismMintableERC721Factory } from "interfaces/universal/IOptimismMintableERC721Factory.sol"; +import { IGovernanceToken } from "interfaces/governance/IGovernanceToken.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IL2StandardBridge } from "interfaces/L2/IL2StandardBridge.sol"; +import { IL2ERC721Bridge } from "interfaces/L2/IL2ERC721Bridge.sol"; +import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { IL2CrossDomainMessenger } from "interfaces/L2/IL2CrossDomainMessenger.sol"; +import { IGasPriceOracle } from "interfaces/L2/IGasPriceOracle.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; + +import { SoulGasToken } from "src/L2/SoulGasToken.sol"; struct L1Dependencies { address payable l1CrossDomainMessengerProxy; @@ -183,6 +180,10 @@ contract L2Genesis is Deployer { if (writeForkGenesisAllocs(_fork, Fork.GRANITE, _mode)) { return; } + + if (writeForkGenesisAllocs(_fork, Fork.HOLOCENE, _mode)) { + return; + } } function writeForkGenesisAllocs(Fork _latest, Fork _current, OutputMode _mode) internal returns (bool isLatest_) { @@ -269,6 +270,7 @@ contract L2Genesis is Deployer { // 1B,1C,1D,1E,1F: not used. setSchemaRegistry(); // 20 setEAS(); // 21 + if (cfg.useSoulGasToken()) setSoulGasToken(); // 800 setGovernanceToken(); // 42: OP (not behind a proxy) if (cfg.useInterop()) { setCrossL2Inbox(); // 22 @@ -277,6 +279,7 @@ contract L2Genesis is Deployer { setETHLiquidity(); // 25 setOptimismSuperchainERC20Factory(); // 26 setOptimismSuperchainERC20Beacon(); // 27 + setSuperchainTokenBridge(); // 28 } } @@ -296,6 +299,44 @@ contract L2Genesis is Deployer { _setImplementationCode(Predeploys.L2_TO_L1_MESSAGE_PASSER); } + /// @notice This predeploy is following the safety invariant #2. + function setSoulGasToken() public { + address impl = Predeploys.predeployToCodeNamespace(Predeploys.SOUL_GAS_TOKEN); + console.log( + "Setting %s implementation at: %s, isSoulBackedByNative:%d", + "SoulGasToken", + impl, + cfg.isSoulBackedByNative() + ); + SoulGasToken token; + if (cfg.isSoulBackedByNative()) { + token = new SoulGasToken({ _isBackedByNative: true }); + } else { + token = new SoulGasToken({ _isBackedByNative: false }); + } + vm.etch(impl, address(token).code); + + /// Reset so its not included state dump + vm.etch(address(token), ""); + vm.resetNonce(address(token)); + + if (cfg.isSoulBackedByNative()) { + SoulGasToken(impl).initialize({ _name: "", _symbol: "", _owner: cfg.proxyAdminOwner() }); + SoulGasToken(Predeploys.SOUL_GAS_TOKEN).initialize({ + _name: "QKC", + _symbol: "QKC", + _owner: cfg.proxyAdminOwner() + }); + } else { + SoulGasToken(impl).initialize({ _name: "", _symbol: "", _owner: cfg.proxyAdminOwner() }); + SoulGasToken(Predeploys.SOUL_GAS_TOKEN).initialize({ + _name: "QKC", + _symbol: "QKC", + _owner: cfg.proxyAdminOwner() + }); + } + } + /// @notice This predeploy is following the safety invariant #1. function setL2CrossDomainMessenger(address payable _l1CrossDomainMessengerProxy) public { address impl = _setImplementationCode(Predeploys.L2_CROSS_DOMAIN_MESSENGER); @@ -576,29 +617,20 @@ contract L2Genesis is Deployer { _setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); } - /// @notice This predeploy is following the safety invariant #2. + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. function setOptimismSuperchainERC20Beacon() internal { address superchainERC20Impl = Predeploys.OPTIMISM_SUPERCHAIN_ERC20; console.log("Setting %s implementation at: %s", "OptimismSuperchainERC20", superchainERC20Impl); vm.etch(superchainERC20Impl, vm.getDeployedCode("OptimismSuperchainERC20.sol:OptimismSuperchainERC20")); - IOptimismSuperchainERC20Beacon beacon = IOptimismSuperchainERC20Beacon( - DeployUtils.create1( - "OptimismSuperchainERC20Beacon", - DeployUtils.encodeConstructor( - abi.encodeCall(IOptimismSuperchainERC20Beacon.__constructor__, (superchainERC20Impl)) - ) - ) - ); - - address beaconImpl = Predeploys.predeployToCodeNamespace(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); - - console.log("Setting %s implementation at: %s", "OptimismSuperchainERC20Beacon", beaconImpl); - vm.etch(beaconImpl, address(beacon).code); + _setImplementationCode(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + } - /// Reset so its not included state dump - vm.etch(address(beacon), ""); - vm.resetNonce(address(beacon)); + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. + function setSuperchainTokenBridge() internal { + _setImplementationCode(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); } /// @notice Sets all the preinstalls. @@ -651,11 +683,7 @@ contract L2Genesis is Deployer { /// @notice Sorts the allocs by address function sortJsonByKeys(string memory _path) internal { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat("cat <<< $(jq -S '.' ", _path, ") > ", _path); - Process.run(commands); + Process.bash(string.concat("cat <<< $(jq -S '.' ", _path, ") > ", _path)); } /// @notice Funds the default dev accounts with ether diff --git a/packages/contracts-bedrock/scripts/autogen/SemverLock.s.sol b/packages/contracts-bedrock/scripts/autogen/SemverLock.s.sol deleted file mode 100644 index 1d5aeb963e3db..0000000000000 --- a/packages/contracts-bedrock/scripts/autogen/SemverLock.s.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { Script } from "forge-std/Script.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { console2 as console } from "forge-std/console2.sol"; -import { Process } from "scripts/libraries/Process.sol"; - -contract SemverLock is Script { - function run() public { - // First, find all contracts with a Semver inheritance. - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = "grep -rl '@custom:semver' src | jq -Rs 'split(\"\\n\") | map(select(length > 0))'"; - string memory rawFiles = string(Process.run(commands)); - - string[] memory files = vm.parseJsonStringArray(rawFiles, ""); - writeSemverLock(files); - } - - /// @dev Writes a Semver lockfile - function writeSemverLock(string[] memory _files) internal { - string memory out; - for (uint256 i; i < _files.length; i++) { - // Use FFI to read the file to remove the need for FS permissions in the foundry.toml. - string[] memory commands = new string[](2); - commands[0] = "cat"; - commands[1] = _files[i]; - string memory fileContents = string(Process.run(commands)); - - // Grab the contract name - commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat("echo \"", _files[i], "\"| sed -E \'s|src/.*/(.+)\\.sol|\\1|\'"); - string memory contractName = string(Process.run(commands)); - - commands[2] = "forge config --json | jq -r .out"; - string memory artifactsDir = string(Process.run(commands)); - - // Handle the case where there are multiple artifacts for a contract. This happens - // when the same contract is compiled with multiple compiler versions. - string memory contractArtifactDir = string.concat(artifactsDir, "/", contractName, ".sol"); - commands[2] = string.concat( - "ls -1 --color=never ", contractArtifactDir, " | jq -R -s -c 'split(\"\n\") | map(select(length > 0))'" - ); - string memory artifactFiles = string(Process.run(commands)); - - string[] memory files = stdJson.readStringArray(artifactFiles, ""); - require(files.length > 0, string.concat("SemverLock: no artifacts found for ", contractName)); - string memory fileName = files[0]; - - // Parse the artifact to get the contract's initcode hash. - bytes memory initCode = vm.getCode(string.concat(artifactsDir, "/", contractName, ".sol/", fileName)); - - // Serialize the initcode hash + sourcecode hash in JSON. - vm.serializeBytes32(_files[i], "initCodeHash", keccak256(initCode)); - string memory obj = vm.serializeBytes32(_files[i], "sourceCodeHash", keccak256(bytes(fileContents))); - - // Serialize the map from the file name -> initcode hash + sourcecode hash in JSON. - out = vm.serializeString("", _files[i], obj); - } - - // Write the semver lockfile. - vm.writeJson(out, "semver-lock.json"); - console.logString("Wrote semver lock file to \"semver-lock.json\"."); - } -} diff --git a/packages/contracts-bedrock/scripts/autogen/generate-semver-lock/main.go b/packages/contracts-bedrock/scripts/autogen/generate-semver-lock/main.go new file mode 100644 index 0000000000000..162f81c840dd2 --- /dev/null +++ b/packages/contracts-bedrock/scripts/autogen/generate-semver-lock/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/crypto" +) + +const semverLockFile = "snapshots/semver-lock.json" + +func main() { + if err := run(); err != nil { + panic(err) + } +} + +func run() error { + // Find semver files + // Execute grep command to find files with @custom:semver + var cmd = exec.Command("bash", "-c", "grep -rl '@custom:semver' src | jq -Rs 'split(\"\n\") | map(select(length > 0))'") + cmdOutput, err := cmd.Output() + if err != nil { + return err + } + + // Parse the JSON array of files + var files []string + if err := json.Unmarshal(cmdOutput, &files); err != nil { + return fmt.Errorf("failed to parse JSON output: %w", err) + } + + // Hash and write to JSON file + // Map to store our JSON output + output := make(map[string]map[string]string) + + // regex to extract contract name from file path + re := regexp.MustCompile(`src/.*/(.+)\.sol`) + + // Get artifacts directory + cmd = exec.Command("forge", "config", "--json") + out, err := cmd.Output() + if err != nil { + return fmt.Errorf("failed to get forge config: %w", err) + } + var config struct { + Out string `json:"out"` + } + if err := json.Unmarshal(out, &config); err != nil { + return fmt.Errorf("failed to parse forge config: %w", err) + } + + for _, file := range files { + // Read file contents + fileContents, err := os.ReadFile(file) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", file, err) + } + + // Extract contract name from file path using regex + matches := re.FindStringSubmatch(file) + if len(matches) < 2 { + return fmt.Errorf("invalid file path format: %s", file) + } + contractName := matches[1] + + // Get artifact files + artifactDir := filepath.Join(config.Out, contractName+".sol") + files, err := os.ReadDir(artifactDir) + if err != nil { + return fmt.Errorf("failed to read artifact directory: %w", err) + } + if len(files) == 0 { + return fmt.Errorf("no artifacts found for %s", contractName) + } + + // Read initcode from artifact + artifactPath := filepath.Join(artifactDir, files[0].Name()) + artifact, err := os.ReadFile(artifactPath) + if err != nil { + return fmt.Errorf("failed to read initcode: %w", err) + } + artifactJson := json.RawMessage(artifact) + var artifactObj struct { + Bytecode struct { + Object string `json:"object"` + } `json:"bytecode"` + } + if err := json.Unmarshal(artifactJson, &artifactObj); err != nil { + return fmt.Errorf("failed to parse artifact: %w", err) + } + + // convert the hex bytecode to a uint8 array / bytes + initCodeBytes, err := hex.DecodeString(strings.TrimPrefix(artifactObj.Bytecode.Object, "0x")) + if err != nil { + return fmt.Errorf("failed to decode hex: %w", err) + } + + // Calculate hashes using Keccak256 + var sourceCode = []byte(strings.TrimSuffix(string(fileContents), "\n")) + initCodeHash := fmt.Sprintf("0x%x", crypto.Keccak256Hash(initCodeBytes)) + sourceCodeHash := fmt.Sprintf("0x%x", crypto.Keccak256Hash(sourceCode)) + + // Store in output map + output[file] = map[string]string{ + "initCodeHash": initCodeHash, + "sourceCodeHash": sourceCodeHash, + } + } + + // Write to JSON file + jsonData, err := json.MarshalIndent(output, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + if err := os.WriteFile(semverLockFile, jsonData, 0644); err != nil { + return fmt.Errorf("failed to write semver lock file: %w", err) + } + + fmt.Printf("Wrote semver lock file to \"%s\".\n", semverLockFile) + return nil +} diff --git a/packages/contracts-bedrock/scripts/checks/check-file-unchanged.sh b/packages/contracts-bedrock/scripts/checks/check-file-unchanged.sh new file mode 100755 index 0000000000000..0949503efe32c --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/check-file-unchanged.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Check if both arguments are provided +if [ $# -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +file_path="$1" +expected_hash="$2" + +# Check if the file exists +if [ ! -f "$file_path" ]; then + echo "Error: File '$file_path' does not exist." + exit 1 +fi + +# Calculate the actual hash of the file +actual_hash=$(openssl dgst -sha256 -r "$file_path" | awk '{print $1}') + +# Compare the hashes +if [ "$actual_hash" = "$expected_hash" ]; then + exit 0 +else + echo "File '$file_path' has changed when it shouldn't have" + echo "Expected hash: $expected_hash" + echo "Actual hash: $actual_hash" + exit 1 +fi diff --git a/packages/contracts-bedrock/scripts/checks/check-foundry-install.sh b/packages/contracts-bedrock/scripts/checks/check-foundry-install.sh deleted file mode 100755 index a2093e936f3ff..0000000000000 --- a/packages/contracts-bedrock/scripts/checks/check-foundry-install.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_BASE=$(dirname "$(dirname "$SCRIPT_DIR")") -MONOREPO_BASE=$(dirname "$(dirname "$CONTRACTS_BASE")") -VERSIONS_FILE="${MONOREPO_BASE}/versions.json" - -if ! command -v jq &> /dev/null -then - # shellcheck disable=SC2006 - echo "Please install jq" >&2 - exit 1 -fi - -if ! command -v forge &> /dev/null -then - # shellcheck disable=SC2006 - echo "Is Foundry not installed? Consider installing via just install-foundry" >&2 - exit 1 -fi - -# Check VERSIONS_FILE has expected foundry property -if ! jq -e '.foundry' "$VERSIONS_FILE" &> /dev/null; then - echo "'foundry' is missing from $VERSIONS_FILE" >&2 - exit 1 -fi - -# Extract the expected foundry version from versions.json -EXPECTED_VERSION=$(jq -r '.foundry' "$VERSIONS_FILE" | cut -c 1-7) -if [ -z "$EXPECTED_VERSION" ]; then - echo "Unable to extract Foundry version from $VERSIONS_FILE" >&2 - exit 1 -fi - -# Extract the installed forge version -INSTALLED_VERSION=$(forge --version | grep -o '[a-f0-9]\{7\}' | head -n 1) - -# Compare the installed timestamp with the expected timestamp -if [ "$INSTALLED_VERSION" = "$EXPECTED_VERSION" ]; then - echo "Foundry version matches the expected version." -else - echo "Mismatch between installed Foundry version ($INSTALLED_VERSION) and expected version ($EXPECTED_VERSION)." - echo "Your version of Foundry may either not be up to date, or it could be a later version." - echo "Running 'just update-foundry' from the repository root will install the expected version." -fi diff --git a/packages/contracts-bedrock/scripts/checks/check-interfaces.sh b/packages/contracts-bedrock/scripts/checks/check-interfaces.sh deleted file mode 100755 index c37783a08b905..0000000000000 --- a/packages/contracts-bedrock/scripts/checks/check-interfaces.sh +++ /dev/null @@ -1,287 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Warn users of Mac OSX who have not ever upgraded bash from the default that they may experience -# performance issues. -if [ "${BASH_VERSINFO[0]}" -lt 5 ]; then - echo "WARNING: your bash installation is very old, and may cause this script to run extremely slowly. Please upgrade bash to at least version 5 if you have performance issues." -fi - -# This script checks for ABI consistency between interfaces and their corresponding contracts. -# It compares the ABIs of interfaces (files starting with 'I') with their implementation contracts, -# excluding certain predefined files. Constructors are expected to be represented in interfaces by a -# pseudo-constructor function `__constructor__(...)` with arguments the same as the contract's constructor. -# The script reports any differences found and exits with an error if inconsistencies are detected. -# NOTE: Script is fast enough but could be parallelized if necessary. - -# Parse flags -no_diff=false -if [[ "${1:-}" == "--no-diff" ]]; then - no_diff=true -fi - -# Grab the directory of the contracts-bedrock package -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_BASE=$(dirname "$(dirname "$SCRIPT_DIR")") - -# Define contracts that should be excluded from the check -EXCLUDE_CONTRACTS=( - # External dependencies - "IERC20" - "IERC721" - "IERC721Enumerable" - "IERC721Upgradeable" - "IERC721Metadata" - "IERC165" - "IERC165Upgradeable" - "ERC721TokenReceiver" - "ERC1155TokenReceiver" - "ERC777TokensRecipient" - "Guard" - "IProxy" - "Vm" - "VmSafe" - "IMulticall3" - "IERC721TokenReceiver" - "IProxyCreationCallback" - "IBeacon" - - # EAS - "IEAS" - "ISchemaResolver" - "ISchemaRegistry" - - # TODO: Interfaces that need to be fixed are below this line - # ---------------------------------------------------------- - - # Inlined interface, needs to be replaced. - "IInitializable" - - # Missing various functions. - "IPreimageOracle" - "ILegacyMintableERC20" - "IOptimismMintableERC20" - "IOptimismMintableERC721" - "IOptimismSuperchainERC20" - - # Doesn't start with "I" - "MintableAndBurnable" - "KontrolCheatsBase" - - # Currently inherit from interface, needs to be fixed. - "IWETH" - "IDelayedWETH" - "IL2ToL2CrossDomainMessenger" - "ICrossL2Inbox" - "ISystemConfigInterop" - - # Enums need to be normalized - "ISequencerFeeVault" - "IBaseFeeVault" - "IL1FeeVault" - "IFeeVault" - - # Solidity complains about receive but contract doens't have it. - "IResolvedDelegateProxy" -) - -# Find all JSON files in the forge-artifacts folder -JSON_FILES=$(find "$CONTRACTS_BASE/forge-artifacts" -type f -name "*.json") - -# Initialize a flag to track if any issues are detected -issues_detected=false - -# Create a temporary file to store files that have already been reported -REPORTED_INTERFACES_FILE=$(mktemp) - -# Clean up the temporary file on exit -cleanup() { - rm -f "$REPORTED_INTERFACES_FILE" -} - -# Trap exit and error signals and call cleanup function -trap cleanup EXIT ERR - -# Check if a contract is excluded -is_excluded() { - for exclude in "${EXCLUDE_CONTRACTS[@]}"; do - if [[ "$exclude" == "$1" ]]; then - return 0 - fi - done - return 1 -} - -# Iterate over all JSON files -for interface_file in $JSON_FILES; do - # Grab the contract name from the file name - contract_name=$(basename "$interface_file" .json | cut -d '.' -f 1) - - # Extract all contract definitions in a single pass - contract_definitions=$(jq -r '.ast.nodes[] | select(.nodeType == "ContractDefinition") | "\(.contractKind),\(.name)"' "$interface_file") - - # Continue if no contract definitions are found - # Can happen in Solidity files that don't declare any contracts/interfaces - if [ -z "$contract_definitions" ]; then - continue - fi - - # Iterate over the found contract definitions and figure out which one - # matches the file name. We do this so that we can figure out if this is an - # interface or a contract based on the contract kind. - found=false - contract_temp="" - contract_kind="" - for definition in $contract_definitions; do - IFS=',' read -r contract_kind contract_temp <<< "$definition" - if [[ "$contract_name" == "$contract_temp" ]]; then - found=true - break - fi - done - - # Continue if no matching contract name is found. Can happen in Solidity - # files where no contracts/interfaces are defined with the same name as the - # file. Still OK because a separate artifact *will* be generated for the - # specific contract/interface. - if [ "$found" = false ]; then - continue - fi - - # If contract kind is not "interface", skip the file - if [ "$contract_kind" != "interface" ]; then - continue - fi - - # If contract name does not start with an "I", throw an error - if [[ "$contract_name" != I* ]]; then - if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then - echo "$contract_name" >> "$REPORTED_INTERFACES_FILE" - if ! is_excluded "$contract_name"; then - echo "Issue found in ABI for interface $contract_name from file $interface_file." - echo "Interface $contract_name does not start with 'I'." - issues_detected=true - fi - fi - continue - fi - - # Extract contract semver - contract_semver=$(jq -r '.ast.nodes[] | select(.nodeType == "PragmaDirective") | .literals | join("")' "$interface_file") - - # If semver is not exactly "solidity^0.8.0", throw an error - if [ "$contract_semver" != "solidity^0.8.0" ]; then - if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then - echo "$contract_name" >> "$REPORTED_INTERFACES_FILE" - if ! is_excluded "$contract_name"; then - echo "Issue found in ABI for interface $contract_name from file $interface_file." - echo "Interface $contract_name does not have correct compiler version (MUST be exactly solidity ^0.8.0)." - issues_detected=true - fi - fi - continue - fi - - # Construct the corresponding contract name by removing the leading "I" - contract_basename=${contract_name:1} - corresponding_contract_file="$CONTRACTS_BASE/forge-artifacts/$contract_basename.sol/$contract_basename.json" - - # Skip the file if the corresponding contract file does not exist - if [ ! -f "$corresponding_contract_file" ]; then - continue - fi - - # Extract and compare ABIs excluding constructors - interface_abi=$(jq '[.abi[]]' < "$interface_file") - contract_abi=$(jq '[.abi[]]' < "$corresponding_contract_file") - - # Function to normalize ABI by replacing interface name with contract name. - # Base contracts aren't allowed to inherit from their interfaces in order - # to guarantee a 1:1 match between interfaces and contracts. This means - # that the interface will redefine types in the base contract. We normalize - # the ABI as if the interface and contract are the same name. - normalize_abi() { - # Here we just remove the leading "I" from any contract, enum, or - # struct type. It's not beautiful but it's good enough for now. It - # would miss certain edge cases like if an interface really is using - # the contract type instead of the interface type but that's unlikely - # to happen in practice and should be an easy fix if it does. - local abi="$1" - - # Remove the leading "I" from types. - abi="${abi//\"internalType\": \"contract I/\"internalType\": \"contract }" - abi="${abi//\"internalType\": \"enum I/\"internalType\": \"enum }" - abi="${abi//\"internalType\": \"struct I/\"internalType\": \"struct }" - - # Handle translating pseudo-constructors. - abi=$(echo "$abi" | jq 'map(if .type == "function" and .name == "__constructor__" then .type = "constructor" | del(.name) | del(.outputs) else . end)') - - echo "$abi" - } - - # Normalize the ABIs - normalized_interface_abi=$(normalize_abi "$interface_abi") - normalized_contract_abi=$(normalize_abi "$contract_abi") - - # Check if the contract ABI has no constructor but the interface is missing __constructor__ - contract_has_constructor=$(echo "$normalized_contract_abi" | jq 'any(.[]; .type == "constructor")') - interface_has_default_pseudo_constructor=$(echo "$normalized_interface_abi" | jq 'any(.[]; .type == "constructor" and .inputs == [])') - - # If any contract has no constructor and its corresponding interface also does not have one, flag it as a detected issue - if [ "$contract_has_constructor" = false ] && [ "$interface_has_default_pseudo_constructor" = false ]; then - if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then - echo "$contract_name" >> "$REPORTED_INTERFACES_FILE" - if ! is_excluded "$contract_name"; then - echo "Issue found in ABI for interface $contract_name from file $interface_file." - echo "Interface $contract_name must have a function named '__constructor__' as the corresponding contract has no constructor in its ABI." - issues_detected=true - fi - fi - continue - fi - - # removes the pseudo constructor json entry from the interface files where the corresponding contract file has no constructor - # this is to ensure it is not flagged as a diff in the next step below - if [ "$contract_has_constructor" = false ] && [ "$interface_has_default_pseudo_constructor" ]; then - normalized_interface_abi=$(echo "$normalized_interface_abi" | jq 'map(select(.type != "constructor"))') - fi - - # Use jq to compare the ABIs - if ! diff_result=$(diff -u <(echo "$normalized_interface_abi" | jq 'sort') <(echo "$normalized_contract_abi" | jq 'sort')); then - if ! grep -q "^$contract_name$" "$REPORTED_INTERFACES_FILE"; then - echo "$contract_name" >> "$REPORTED_INTERFACES_FILE" - if ! is_excluded "$contract_name"; then - echo "Issue found in ABI for interface $contract_name from file $interface_file." - echo "Differences found in ABI between interface $contract_name and actual contract $contract_basename." - if [ "$no_diff" = false ]; then - echo "$diff_result" - fi - issues_detected=true - fi - fi - continue - fi -done - -# Check for unnecessary exclusions -for exclude_item in "${EXCLUDE_CONTRACTS[@]}"; do - if ! grep -q "^$exclude_item$" "$REPORTED_INTERFACES_FILE"; then - echo "Warning: $exclude_item is in the exclude list but WAS NOT reported as an issue. It" - echo "may be unnecessary in the EXCLUDE_CONTRACTS list, but you MUST verify this before" - echo "removing it by performing a clean and full build before re-running this script." - fi -done - -# Fail the script if any issues were detected -if [ "$issues_detected" = true ]; then - echo "Issues were detected while validating interface files." - echo "If the interface is an external dependency or should otherwise be excluded from this" - echo "check, add the interface name to the EXCLUDE_CONTRACTS list in the script. This will prevent" - echo "the script from comparing it against a corresponding contract." - echo "IMPORTANT: Interface files are NOT yet generated automatically. You must fix any" - echo "listed discrepancies manually by updating the specified interface file. Automated" - echo "interface generation is dependent on a few Forge bug fixes." - exit 1 -else - exit 0 -fi diff --git a/packages/contracts-bedrock/scripts/checks/check-kontrol-deployment.sh b/packages/contracts-bedrock/scripts/checks/check-kontrol-deployment.sh deleted file mode 100755 index bfa78ae35a700..0000000000000 --- a/packages/contracts-bedrock/scripts/checks/check-kontrol-deployment.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# This script checks if the KontrolDeployment.sol file has changed. Removal of -# the DeploymentSummary.t.sol test file means that our primary risk vector for -# KontrolDeployment.sol is an *accidental* change to the file. Changes must -# therefore be explicitly acknowledged by bumping the hash below. - -# Get relevant directories -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -CONTRACTS_BASE=$(dirname "$(dirname "$SCRIPT_DIR")") - -# Generate the SHA-512 hash using OpenSSL (very portable) -generated_hash=$(openssl dgst -sha512 "${CONTRACTS_BASE}/test/kontrol/deployment/KontrolDeployment.sol" | awk '{print $2}') - -# Define the known hash -known_hash="1664d9c22266b55b43086fa03c0e9d0447b092abc86cba79b86ad36c49167062c2b58a78757a20a5fd257d307599edce8f8f604cc6b2ee86715144015a8c977d" - -# Compare the generated hash with the known hash -if [ "$generated_hash" = "$known_hash" ]; then - echo "KontrolDeployment.sol matches the known hash." -else - echo "KontrolDeployment.sol does not match the known hash. Please update the known hash." - echo "Old hash: $known_hash" - echo "New hash: $generated_hash" - exit 1 -fi diff --git a/packages/contracts-bedrock/scripts/checks/check-kontrol-summaries-unchanged.sh b/packages/contracts-bedrock/scripts/checks/check-kontrol-summaries-unchanged.sh new file mode 100755 index 0000000000000..32c764c188a8e --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/check-kontrol-summaries-unchanged.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs the file unchanged script for each of the Kontrol summary files. +# Update these hashes if you have changed the summary files deliberately. +# Use `openssl dgst -sha256` to generate the hash for a file. +./scripts/checks/check-file-unchanged.sh ./test/kontrol/proofs/utils/DeploymentSummary.sol 73e1ed1f3c8bd9e46bb348b774443458206b2399e4eac303e2a92ac987eeefc6 +./scripts/checks/check-file-unchanged.sh ./test/kontrol/proofs/utils/DeploymentSummaryCode.sol ffe7298024464a6a282ceb549db401cce4b36fe3342113250a6c674a3b4203ba +./scripts/checks/check-file-unchanged.sh ./test/kontrol/proofs/utils/DeploymentSummaryFaultProofs.sol 5ec8b19a00b47f15a2a8c8d632ab83933e19b0c2bbb04c905a2cda926e56038f +./scripts/checks/check-file-unchanged.sh ./test/kontrol/proofs/utils/DeploymentSummaryFaultProofsCode.sol 1215a561a2bffb5af62b532a2608c4048a22368e505f7ae1d237c16b4ef6a45e diff --git a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh index 81e7c6476d3a1..05aad2a27d68f 100755 --- a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh +++ b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh @@ -9,20 +9,39 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) source "$SCRIPT_DIR/utils/semver-utils.sh" # Path to semver-lock.json. -SEMVER_LOCK="semver-lock.json" +SEMVER_LOCK="snapshots/semver-lock.json" + +# Define excluded contracts. +EXCLUDED_CONTRACTS=( + "src/vendor/asterisc/RISCV.sol" +) + +# Helper function to check if a contract is excluded. +is_excluded() { + local contract="$1" + for excluded in "${EXCLUDED_CONTRACTS[@]}"; do + if [[ "$contract" == "$excluded" ]]; then + return 0 + fi + done + return 1 +} # Create a temporary directory. temp_dir=$(mktemp -d) trap 'rm -rf "$temp_dir"' EXIT # Exit early if semver-lock.json has not changed. -if ! { git diff origin/develop...HEAD --name-only; git diff --name-only; git diff --cached --name-only; } | grep -q "$SEMVER_LOCK"; then +if ! { git diff origin/op-es...HEAD --name-only; git diff --name-only; git diff --cached --name-only; } | grep -q "$SEMVER_LOCK"; then echo "No changes detected in semver-lock.json" exit 0 fi # Get the upstream semver-lock.json. -git show origin/develop:packages/contracts-bedrock/semver-lock.json > "$temp_dir/upstream_semver_lock.json" +if ! git show origin/op-es:packages/contracts-bedrock/snapshots/semver-lock.json > "$temp_dir/upstream_semver_lock.json" 2>/dev/null; then + echo "❌ Error: Could not find semver-lock.json in the snapshots/ directory of op-es branch" + exit 1 +fi # Copy the local semver-lock.json. cp "$SEMVER_LOCK" "$temp_dir/local_semver_lock.json" @@ -46,6 +65,11 @@ has_errors=false # Check each changed contract for a semver version change. for contract in $changed_contracts; do + # Skip excluded contracts. + if is_excluded "$contract"; then + continue + fi + # Check if the contract file exists. if [ ! -f "$contract" ]; then echo "❌ Error: Contract file $contract not found" @@ -56,7 +80,7 @@ for contract in $changed_contracts; do # Extract the old and new source files. old_source_file="$temp_dir/old_${contract##*/}" new_source_file="$temp_dir/new_${contract##*/}" - git show origin/develop:packages/contracts-bedrock/"$contract" > "$old_source_file" 2>/dev/null || true + git show origin/op-es:packages/contracts-bedrock/"$contract" > "$old_source_file" 2>/dev/null || true cp "$contract" "$new_source_file" # Extract the old and new versions. diff --git a/packages/contracts-bedrock/scripts/checks/check-snapshots.sh b/packages/contracts-bedrock/scripts/checks/check-snapshots.sh index 66a04dc331d41..18557eba52a52 100755 --- a/packages/contracts-bedrock/scripts/checks/check-snapshots.sh +++ b/packages/contracts-bedrock/scripts/checks/check-snapshots.sh @@ -3,7 +3,7 @@ set -euo pipefail # Check for the --no-build flag # Generate snapshots -if [ "$1" == "--no-build" ]; then +if [ "${1:-}" == "--no-build" ]; then just snapshots-no-build else just snapshots diff --git a/packages/contracts-bedrock/scripts/checks/common/util.go b/packages/contracts-bedrock/scripts/checks/common/util.go new file mode 100644 index 0000000000000..131dff3b8987f --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/common/util.go @@ -0,0 +1,124 @@ +package common + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "sync/atomic" + + "github.com/bmatcuk/doublestar/v4" + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" + "golang.org/x/sync/errgroup" +) + +type ErrorReporter struct { + hasErr atomic.Bool + outMtx sync.Mutex +} + +func NewErrorReporter() *ErrorReporter { + return &ErrorReporter{} +} + +func (e *ErrorReporter) Fail(msg string, args ...any) { + e.outMtx.Lock() + // Useful for suppressing error reporting in tests + if os.Getenv("SUPPRESS_ERROR_REPORTER") == "" { + _, _ = fmt.Fprintf(os.Stderr, "❌ "+msg+"\n", args...) + } + e.outMtx.Unlock() + e.hasErr.Store(true) +} + +func (e *ErrorReporter) HasError() bool { + return e.hasErr.Load() +} + +type FileProcessor func(path string) []error + +func ProcessFiles(files map[string]string, processor FileProcessor) error { + g := errgroup.Group{} + g.SetLimit(runtime.NumCPU()) + + reporter := NewErrorReporter() + for name, path := range files { + name, path := name, path // Capture loop variables + g.Go(func() error { + if errs := processor(path); len(errs) > 0 { + for _, err := range errs { + reporter.Fail("%s: %v", name, err) + } + } + return nil + }) + } + + err := g.Wait() + if err != nil { + return fmt.Errorf("processing failed: %w", err) + } + if reporter.HasError() { + return fmt.Errorf("processing failed") + } + return nil +} + +func ProcessFilesGlob(includes, excludes []string, processor FileProcessor) error { + files, err := FindFiles(includes, excludes) + if err != nil { + return err + } + return ProcessFiles(files, processor) +} + +func FindFiles(includes, excludes []string) (map[string]string, error) { + included := make(map[string]string) + excluded := make(map[string]struct{}) + + // Get all included files + for _, pattern := range includes { + matches, err := doublestar.Glob(os.DirFS("."), pattern) + if err != nil { + return nil, fmt.Errorf("glob pattern error: %w", err) + } + for _, match := range matches { + name := filepath.Base(match) + included[name] = match + } + } + + // Get all excluded files + for _, pattern := range excludes { + matches, err := doublestar.Glob(os.DirFS("."), pattern) + if err != nil { + return nil, fmt.Errorf("glob pattern error: %w", err) + } + for _, match := range matches { + excluded[filepath.Base(match)] = struct{}{} + } + } + + // Remove excluded files from result + for name := range excluded { + delete(included, name) + } + + return included, nil +} + +func ReadForgeArtifact(path string) (*solc.ForgeArtifact, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read artifact: %w", err) + } + + var artifact solc.ForgeArtifact + if err := json.Unmarshal(data, &artifact); err != nil { + return nil, fmt.Errorf("failed to parse artifact: %w", err) + } + + return &artifact, nil +} diff --git a/packages/contracts-bedrock/scripts/checks/common/util_test.go b/packages/contracts-bedrock/scripts/checks/common/util_test.go new file mode 100644 index 0000000000000..4defc1c70454c --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/common/util_test.go @@ -0,0 +1,180 @@ +package common + +import ( + "os" + "path/filepath" + "testing" +) + +func TestErrorReporter(t *testing.T) { + os.Setenv("SUPPRESS_ERROR_REPORTER", "1") + defer os.Unsetenv("SUPPRESS_ERROR_REPORTER") + + reporter := NewErrorReporter() + + if reporter.HasError() { + t.Error("new reporter should not have errors") + } + + reporter.Fail("test error") + + if !reporter.HasError() { + t.Error("reporter should have error after Fail") + } +} + +func TestProcessFiles(t *testing.T) { + os.Setenv("SUPPRESS_ERROR_REPORTER", "1") + defer os.Unsetenv("SUPPRESS_ERROR_REPORTER") + + files := map[string]string{ + "file1": "path1", + "file2": "path2", + } + + // Test successful processing + err := ProcessFiles(files, func(path string) []error { + return nil + }) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + // Test error handling + err = ProcessFiles(files, func(path string) []error { + var errors []error + errors = append(errors, os.ErrNotExist) + return errors + }) + if err == nil { + t.Error("expected error, got nil") + } +} + +func TestProcessFilesGlob(t *testing.T) { + os.Setenv("SUPPRESS_ERROR_REPORTER", "1") + defer os.Unsetenv("SUPPRESS_ERROR_REPORTER") + + // Create test directory structure + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatal(err) + } + + // Create test files + files := map[string]string{ + "test1.txt": "content1", + "test2.txt": "content2", + "skip.txt": "content3", + } + + for name, content := range files { + if err := os.WriteFile(name, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + // Test processing with includes and excludes + includes := []string{"*.txt"} + excludes := []string{"skip.txt"} + + processedFiles := make(map[string]bool) + err := ProcessFilesGlob(includes, excludes, func(path string) []error { + processedFiles[filepath.Base(path)] = true + return nil + }) + + if err != nil { + t.Errorf("ProcessFiles failed: %v", err) + } + + // Verify results + if len(processedFiles) != 2 { + t.Errorf("expected 2 processed files, got %d", len(processedFiles)) + } + if !processedFiles["test1.txt"] { + t.Error("expected to process test1.txt") + } + if !processedFiles["test2.txt"] { + t.Error("expected to process test2.txt") + } + if processedFiles["skip.txt"] { + t.Error("skip.txt should have been excluded") + } +} + +func TestFindFiles(t *testing.T) { + // Create test directory structure + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatal(err) + } + + // Create test files + files := map[string]string{ + "test1.txt": "content1", + "test2.txt": "content2", + "skip.txt": "content3", + } + + for name, content := range files { + if err := os.WriteFile(name, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + // Test finding files + includes := []string{"*.txt"} + excludes := []string{"skip.txt"} + + found, err := FindFiles(includes, excludes) + if err != nil { + t.Fatalf("FindFiles failed: %v", err) + } + + // Verify results + if len(found) != 2 { + t.Errorf("expected 2 files, got %d", len(found)) + } + if _, exists := found["test1.txt"]; !exists { + t.Error("expected to find test1.txt") + } + if _, exists := found["test2.txt"]; !exists { + t.Error("expected to find test2.txt") + } + if _, exists := found["skip.txt"]; exists { + t.Error("skip.txt should have been excluded") + } +} + +func TestReadForgeArtifact(t *testing.T) { + // Create a temporary test artifact + tmpDir := t.TempDir() + artifactContent := `{ + "abi": [], + "bytecode": { + "object": "0x123" + }, + "deployedBytecode": { + "object": "0x456" + } + }` + tmpFile := filepath.Join(tmpDir, "Test.json") + if err := os.WriteFile(tmpFile, []byte(artifactContent), 0644); err != nil { + t.Fatal(err) + } + + // Test processing + artifact, err := ReadForgeArtifact(tmpFile) + if err != nil { + t.Fatalf("ReadForgeArtifact failed: %v", err) + } + + // Verify results + if artifact.Bytecode.Object != "0x123" { + t.Errorf("expected bytecode '0x123', got %q", artifact.Bytecode.Object) + } + if artifact.DeployedBytecode.Object != "0x456" { + t.Errorf("expected deployed bytecode '0x456', got %q", artifact.DeployedBytecode.Object) + } +} diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index 004f1d5c03cdc..1eadadd3da0a8 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "os" "path/filepath" "runtime" @@ -26,10 +27,10 @@ var excludeContracts = []string{ "IEAS", "ISchemaResolver", "ISchemaRegistry", // TODO: Interfaces that need to be fixed - "IInitializable", "IPreimageOracle", "ILegacyMintableERC20", "IOptimismMintableERC20", - "IOptimismMintableERC721", "IOptimismSuperchainERC20", "MintableAndBurnable", - "KontrolCheatsBase", "IWETH", "IDelayedWETH", "IL2ToL2CrossDomainMessenger", - "ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy", + + "IInitializable", "IOptimismMintableERC20", "ILegacyMintableERC20", + "KontrolCheatsBase", "ISystemConfigInterop", "IResolvedDelegateProxy", + "IERC20Upgradeable", "ISoulGasToken", } type ContractDefinition struct { @@ -303,6 +304,7 @@ func compareABIs(abi1, abi2 json.RawMessage) (bool, error) { // Compare using go-cmp diff := cmp.Diff(data1, data2) if diff != "" { + log.Printf("ABI diff: %s", diff) return false, nil } return true, nil diff --git a/packages/contracts-bedrock/scripts/checks/names/main.go b/packages/contracts-bedrock/scripts/checks/names/main.go deleted file mode 100644 index 1a0be03f33f29..0000000000000 --- a/packages/contracts-bedrock/scripts/checks/names/main.go +++ /dev/null @@ -1,164 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "unicode" -) - -type Check func(parts []string) bool - -type CheckInfo struct { - check Check - error string -} - -var checks = []CheckInfo{ - { - error: "test name parts should be in camelCase", - check: func(parts []string) bool { - for _, part := range parts { - if len(part) > 0 && unicode.IsUpper(rune(part[0])) { - return false - } - } - return true - }, - }, - { - error: "test names should have either 3 or 4 parts, each separated by underscores", - check: func(parts []string) bool { - return len(parts) == 3 || len(parts) == 4 - }, - }, - { - error: "test names should begin with \"test\", \"testFuzz\", or \"testDiff\"", - check: func(parts []string) bool { - return parts[0] == "test" || parts[0] == "testFuzz" || parts[0] == "testDiff" - }, - }, - { - error: "test names should end with either \"succeeds\", \"reverts\", \"fails\", \"works\" or \"benchmark[_num]\"", - check: func(parts []string) bool { - last := parts[len(parts)-1] - if last == "succeeds" || last == "reverts" || last == "fails" || last == "works" { - return true - } - if len(parts) >= 2 && parts[len(parts)-2] == "benchmark" { - _, err := strconv.Atoi(last) - return err == nil - } - return last == "benchmark" - }, - }, - { - error: "failure tests should have 4 parts, third part should indicate the reason for failure", - check: func(parts []string) bool { - last := parts[len(parts)-1] - return len(parts) == 4 || (last != "reverts" && last != "fails") - }, - }, -} - -func main() { - cmd := exec.Command("forge", "config", "--json") - output, err := cmd.Output() - if err != nil { - fmt.Printf("Error executing forge config: %v\n", err) - os.Exit(1) - } - - var config map[string]interface{} - err = json.Unmarshal(output, &config) - if err != nil { - fmt.Printf("Error parsing forge config: %v\n", err) - os.Exit(1) - } - - outDir, ok := config["out"].(string) - if !ok { - outDir = "out" - } - - fmt.Println("Success:") - var errors []string - - err = filepath.Walk(outDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() { - return nil - } - - data, err := os.ReadFile(path) - if err != nil { - return err - } - - var artifact map[string]interface{} - err = json.Unmarshal(data, &artifact) - if err != nil { - return nil // Skip files that are not valid JSON - } - - abi, ok := artifact["abi"].([]interface{}) - if !ok { - return nil - } - - isTest := false - for _, element := range abi { - if elem, ok := element.(map[string]interface{}); ok { - if elem["name"] == "IS_TEST" { - isTest = true - break - } - } - } - - if isTest { - success := true - for _, element := range abi { - if elem, ok := element.(map[string]interface{}); ok { - if elem["type"] == "function" { - name, ok := elem["name"].(string) - if !ok || !strings.HasPrefix(name, "test") { - continue - } - - parts := strings.Split(name, "_") - for _, check := range checks { - if !check.check(parts) { - errors = append(errors, fmt.Sprintf("%s#%s: %s", path, name, check.error)) - success = false - } - } - } - } - } - - if success { - fmt.Printf(" - %s\n", filepath.Base(path[:len(path)-len(filepath.Ext(path))])) - } - } - - return nil - }) - - if err != nil { - fmt.Printf("Error walking the path %q: %v\n", outDir, err) - os.Exit(1) - } - - if len(errors) > 0 { - fmt.Println(strings.Join(errors, "\n")) - os.Exit(1) - } -} diff --git a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go deleted file mode 100644 index d1e2153c02ef5..0000000000000 --- a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go +++ /dev/null @@ -1,215 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - "sync" - "sync/atomic" -) - -type ArtifactsWrapper struct { - RawMetadata string `json:"rawMetadata"` -} - -type Artifacts struct { - Output struct { - Devdoc struct { - StateVariables struct { - Version struct { - Semver string `json:"custom:semver"` - } `json:"version"` - } `json:"stateVariables,omitempty"` - Methods struct { - Version struct { - Semver string `json:"custom:semver"` - } `json:"version()"` - } `json:"methods,omitempty"` - } `json:"devdoc"` - } `json:"output"` -} - -var ConstantVersionPattern = regexp.MustCompile(`string.*constant.*version\s+=\s+"([^"]+)";`) - -var FunctionVersionPattern = regexp.MustCompile(`^\s+return\s+"((?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)";$`) - -var InteropVersionPattern = regexp.MustCompile(`^\s+return\s+string\.concat\(super\.version\(\), "((.*)\+interop(.*)?)"\);`) - -func main() { - if err := run(); err != nil { - writeStderr("an error occurred: %v", err) - os.Exit(1) - } -} - -func writeStderr(msg string, args ...any) { - _, _ = fmt.Fprintf(os.Stderr, msg+"\n", args...) -} - -func run() error { - cwd, err := os.Getwd() - if err != nil { - return fmt.Errorf("failed to get current working directory: %w", err) - } - - writeStderr("working directory: %s", cwd) - - artifactsDir := filepath.Join(cwd, "forge-artifacts") - srcDir := filepath.Join(cwd, "src") - - artifactFiles, err := glob(artifactsDir, ".json") - if err != nil { - return fmt.Errorf("failed to get artifact files: %w", err) - } - contractFiles, err := glob(srcDir, ".sol") - if err != nil { - return fmt.Errorf("failed to get contract files: %w", err) - } - - var hasErr int32 - var outMtx sync.Mutex - fail := func(msg string, args ...any) { - outMtx.Lock() - writeStderr("❌ "+msg, args...) - outMtx.Unlock() - atomic.StoreInt32(&hasErr, 1) - } - - sem := make(chan struct{}, runtime.NumCPU()) - for contractName, artifactPath := range artifactFiles { - contractName := contractName - artifactPath := artifactPath - - sem <- struct{}{} - - go func() { - defer func() { - <-sem - }() - - af, err := os.Open(artifactPath) - if err != nil { - fail("%s: failed to open contract artifact: %v", contractName, err) - return - } - defer af.Close() - - var wrapper ArtifactsWrapper - if err := json.NewDecoder(af).Decode(&wrapper); err != nil { - fail("%s: failed to parse artifact file: %v", contractName, err) - return - } - - if wrapper.RawMetadata == "" { - return - } - - var artifactData Artifacts - if err := json.Unmarshal([]byte(wrapper.RawMetadata), &artifactData); err != nil { - fail("%s: failed to unwrap artifact metadata: %v", contractName, err) - return - } - - artifactVersion := artifactData.Output.Devdoc.StateVariables.Version.Semver - - isConstant := true - if artifactData.Output.Devdoc.StateVariables.Version.Semver == "" { - artifactVersion = artifactData.Output.Devdoc.Methods.Version.Semver - isConstant = false - } - - if artifactVersion == "" { - return - } - - contractPath := contractFiles[contractName] - if contractPath == "" { - fail("%s: Source file not found", contractName) - return - } - - cf, err := os.Open(contractPath) - if err != nil { - fail("%s: failed to open contract source: %v", contractName, err) - return - } - defer cf.Close() - - sourceData, err := io.ReadAll(cf) - if err != nil { - fail("%s: failed to read contract source: %v", contractName, err) - return - } - - var sourceVersion string - - if isConstant { - sourceVersion = findLine(sourceData, ConstantVersionPattern) - } else { - sourceVersion = findLine(sourceData, FunctionVersionPattern) - } - - // Need to define a special case for interop contracts since they technically - // use an invalid semver format. Checking for sourceVersion == "" allows the - // team to update the format to a valid semver format in the future without - // needing to change this program. - if sourceVersion == "" && strings.HasSuffix(contractName, "Interop") { - sourceVersion = findLine(sourceData, InteropVersionPattern) - } - - if sourceVersion == "" { - fail("%s: version not found in source", contractName) - return - } - - if sourceVersion != artifactVersion { - fail("%s: version mismatch: source=%s, artifact=%s", contractName, sourceVersion, artifactVersion) - return - } - - _, _ = fmt.Fprintf(os.Stderr, "✅ %s: code: %s, artifact: %s\n", contractName, sourceVersion, artifactVersion) - }() - } - - for i := 0; i < cap(sem); i++ { - sem <- struct{}{} - } - - if atomic.LoadInt32(&hasErr) == 1 { - return fmt.Errorf("semver check failed, see logs above") - } - - return nil -} - -func glob(dir string, ext string) (map[string]string, error) { - out := make(map[string]string) - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && filepath.Ext(path) == ext { - out[strings.TrimSuffix(filepath.Base(path), ext)] = path - } - return nil - }) - if err != nil { - return nil, fmt.Errorf("failed to walk directory: %w", err) - } - return out, nil -} - -func findLine(in []byte, pattern *regexp.Regexp) string { - scanner := bufio.NewScanner(bytes.NewReader(in)) - for scanner.Scan() { - match := pattern.FindStringSubmatch(scanner.Text()) - if len(match) > 0 { - return match[1] - } - } - return "" -} diff --git a/packages/contracts-bedrock/scripts/checks/semver-natspec/main_test.go b/packages/contracts-bedrock/scripts/checks/semver-natspec/main_test.go deleted file mode 100644 index 7a8872d76d780..0000000000000 --- a/packages/contracts-bedrock/scripts/checks/semver-natspec/main_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package main - -import ( - "regexp" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestRegexes(t *testing.T) { - t.Run("ConstantVersionPattern", func(t *testing.T) { - testRegex(t, ConstantVersionPattern, []regexTest{ - { - name: "constant version", - input: `string constant version = "1.2.3";`, - capture: "1.2.3", - }, - { - name: "constant version with weird spaces", - input: ` string constant version = "1.2.3";`, - capture: "1.2.3", - }, - { - name: "constant version with visibility", - input: `string public constant version = "1.2.3";`, - capture: "1.2.3", - }, - { - name: "different variable name", - input: `string constant VERSION = "1.2.3";`, - capture: "", - }, - { - name: "different type", - input: `uint constant version = 1;`, - capture: "", - }, - { - name: "not constant", - input: `string version = "1.2.3";`, - capture: "", - }, - { - name: "unterminated", - input: `string constant version = "1.2.3"`, - capture: "", - }, - }) - }) - - t.Run("FunctionVersionPattern", func(t *testing.T) { - testRegex(t, FunctionVersionPattern, []regexTest{ - { - name: "function version", - input: ` return "1.2.3";`, - capture: "1.2.3", - }, - { - name: "function version with weird spaces", - input: ` return "1.2.3";`, - capture: "1.2.3", - }, - { - name: "function version with prerelease", - input: ` return "1.2.3-alpha.1";`, - capture: "1.2.3-alpha.1", - }, - { - name: "invalid semver", - input: ` return "1.2.cabdab";`, - capture: "", - }, - { - name: "not a return statement", - input: `function foo()`, - capture: "", - }, - }) - }) - - t.Run("InteropVersionPattern", func(t *testing.T) { - testRegex(t, InteropVersionPattern, []regexTest{ - { - name: "interop version", - input: ` return string.concat(super.version(), "+interop");`, - capture: "+interop", - }, - { - name: "interop version but as a valid semver", - input: ` return string.concat(super.version(), "0.0.0+interop");`, - capture: "0.0.0+interop", - }, - { - name: "not an interop version", - input: ` return string.concat(super.version(), "hello!");`, - capture: "", - }, - { - name: "invalid syntax", - input: ` return string.concat(super.version(), "0.0.0+interop`, - capture: "", - }, - { - name: "something else is concatted", - input: ` return string.concat("superduper", "mart");`, - capture: "", - }, - }) - }) -} - -type regexTest struct { - name string - input string - capture string -} - -func testRegex(t *testing.T, re *regexp.Regexp, tests []regexTest) { - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require.Equal(t, test.capture, findLine([]byte(test.input), re)) - }) - } -} diff --git a/packages/contracts-bedrock/scripts/checks/spacers/main.go b/packages/contracts-bedrock/scripts/checks/spacers/main.go index 3360bda74d394..daf617defb932 100644 --- a/packages/contracts-bedrock/scripts/checks/spacers/main.go +++ b/packages/contracts-bedrock/scripts/checks/spacers/main.go @@ -1,175 +1,123 @@ package main import ( - "encoding/json" "fmt" "os" - "path/filepath" "regexp" "strconv" "strings" -) - -// directoryPath is the path to the artifacts directory. -// It can be configured as the first argument to the script or -// defaults to the forge-artifacts directory. -var directoryPath string - -func init() { - if len(os.Args) > 1 { - directoryPath = os.Args[1] - } else { - currentDir, _ := os.Getwd() - directoryPath = filepath.Join(currentDir, "forge-artifacts") - } -} - -// skipped returns true if the contract should be skipped when inspecting its storage layout. -func skipped(contractName string) bool { - return strings.Contains(contractName, "CrossDomainMessengerLegacySpacer") -} - -// variableInfo represents the parsed variable information. -type variableInfo struct { - name string - slot int - offset int - length int -} -// parseVariableInfo parses out variable info from the variable structure in standard compiler json output. -func parseVariableInfo(variable map[string]interface{}) (variableInfo, error) { - var info variableInfo - var err error + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/scripts/checks/common" +) - info.name = variable["label"].(string) - info.slot, err = strconv.Atoi(variable["slot"].(string)) - if err != nil { - return info, err +func parseVariableLength(variableType string, types map[string]solc.StorageLayoutType) (int, error) { + if t, exists := types[variableType]; exists { + return int(t.NumberOfBytes), nil } - info.offset = int(variable["offset"].(float64)) - variableType := variable["type"].(string) if strings.HasPrefix(variableType, "t_mapping") { - info.length = 32 + return 32, nil } else if strings.HasPrefix(variableType, "t_uint") { re := regexp.MustCompile(`uint(\d+)`) matches := re.FindStringSubmatch(variableType) if len(matches) > 1 { bitSize, _ := strconv.Atoi(matches[1]) - info.length = bitSize / 8 + return bitSize / 8, nil } } else if strings.HasPrefix(variableType, "t_bytes_") { - info.length = 32 + return 32, nil } else if strings.HasPrefix(variableType, "t_bytes") { re := regexp.MustCompile(`bytes(\d+)`) matches := re.FindStringSubmatch(variableType) if len(matches) > 1 { - info.length, _ = strconv.Atoi(matches[1]) + return strconv.Atoi(matches[1]) } } else if strings.HasPrefix(variableType, "t_address") { - info.length = 20 + return 20, nil } else if strings.HasPrefix(variableType, "t_bool") { - info.length = 1 + return 1, nil } else if strings.HasPrefix(variableType, "t_array") { re := regexp.MustCompile(`^t_array\((\w+)\)(\d+)`) matches := re.FindStringSubmatch(variableType) if len(matches) > 2 { innerType := matches[1] size, _ := strconv.Atoi(matches[2]) - innerInfo, err := parseVariableInfo(map[string]interface{}{ - "label": variable["label"], - "offset": variable["offset"], - "slot": variable["slot"], - "type": innerType, - }) + length, err := parseVariableLength(innerType, types) if err != nil { - return info, err + return 0, err } - info.length = innerInfo.length * size + return length * size, nil } - } else { - return info, fmt.Errorf("%s: unsupported type %s, add it to the script", info.name, variableType) } - return info, nil + return 0, fmt.Errorf("unsupported type %s, add it to the script", variableType) } -func main() { - err := filepath.Walk(directoryPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() || strings.Contains(path, "t.sol") { - return nil - } - - raw, err := os.ReadFile(path) - if err != nil { - return err - } - - var artifact map[string]interface{} - err = json.Unmarshal(raw, &artifact) - if err != nil { - return err - } - - storageLayout, ok := artifact["storageLayout"].(map[string]interface{}) - if !ok { - return nil - } +func validateSpacer(variable solc.StorageLayoutEntry, types map[string]solc.StorageLayoutType) []error { + var errors []error - storage, ok := storageLayout["storage"].([]interface{}) - if !ok { - return nil - } + parts := strings.Split(variable.Label, "_") + if len(parts) != 4 { + return []error{fmt.Errorf("invalid spacer name format: %s", variable.Label)} + } - for _, v := range storage { - variable := v.(map[string]interface{}) - fqn := variable["contract"].(string) + expectedSlot, _ := strconv.Atoi(parts[1]) + expectedOffset, _ := strconv.Atoi(parts[2]) + expectedLength, _ := strconv.Atoi(parts[3]) - if skipped(fqn) { - continue - } + actualLength, err := parseVariableLength(variable.Type, types) + if err != nil { + return []error{err} + } - label := variable["label"].(string) - if strings.HasPrefix(label, "spacer_") { - parts := strings.Split(label, "_") - if len(parts) != 4 { - return fmt.Errorf("invalid spacer name format: %s", label) - } + if int(variable.Slot) != expectedSlot { + errors = append(errors, fmt.Errorf("%s %s is in slot %d but should be in %d", + variable.Contract, variable.Label, variable.Slot, expectedSlot)) + } - slot, _ := strconv.Atoi(parts[1]) - offset, _ := strconv.Atoi(parts[2]) - length, _ := strconv.Atoi(parts[3]) + if int(variable.Offset) != expectedOffset { + errors = append(errors, fmt.Errorf("%s %s is at offset %d but should be at %d", + variable.Contract, variable.Label, variable.Offset, expectedOffset)) + } - variableInfo, err := parseVariableInfo(variable) - if err != nil { - return err - } + if actualLength != expectedLength { + errors = append(errors, fmt.Errorf("%s %s is %d bytes long but should be %d", + variable.Contract, variable.Label, actualLength, expectedLength)) + } - if slot != variableInfo.slot { - return fmt.Errorf("%s %s is in slot %d but should be in %d", fqn, label, variableInfo.slot, slot) - } + return errors +} - if offset != variableInfo.offset { - return fmt.Errorf("%s %s is at offset %d but should be at %d", fqn, label, variableInfo.offset, offset) - } +func processFile(path string) []error { + artifact, err := common.ReadForgeArtifact(path) + if err != nil { + return []error{err} + } - if length != variableInfo.length { - return fmt.Errorf("%s %s is %d bytes long but should be %d", fqn, label, variableInfo.length, length) - } + if artifact.StorageLayout == nil { + return nil + } - fmt.Printf("%s.%s is valid\n", fqn, label) + var errors []error + for _, variable := range artifact.StorageLayout.Storage { + if strings.HasPrefix(variable.Label, "spacer_") { + if errs := validateSpacer(variable, artifact.StorageLayout.Types); len(errs) > 0 { + errors = append(errors, errs...) + continue } } + } - return nil - }) + return errors +} - if err != nil { +func main() { + if err := common.ProcessFilesGlob( + []string{"forge-artifacts/**/*.json"}, + []string{"forge-artifacts/**/CrossDomainMessengerLegacySpacer{0,1}.json"}, + processFile, + ); err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) } diff --git a/packages/contracts-bedrock/scripts/checks/spacers/main_test.go b/packages/contracts-bedrock/scripts/checks/spacers/main_test.go new file mode 100644 index 0000000000000..7548fa80d0319 --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/spacers/main_test.go @@ -0,0 +1,167 @@ +package main + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" + "github.com/stretchr/testify/require" +) + +func Test_parseVariableLength(t *testing.T) { + tests := []struct { + name string + variableType string + types map[string]solc.StorageLayoutType + expected int + expectError bool + }{ + { + name: "uses type from map", + variableType: "t_custom", + types: map[string]solc.StorageLayoutType{ + "t_custom": {NumberOfBytes: 16}, + }, + expected: 16, + }, + { + name: "mapping type", + variableType: "t_mapping(address,uint256)", + expected: 32, + }, + { + name: "uint type", + variableType: "t_uint256", + expected: 32, + }, + { + name: "bytes_ type", + variableType: "t_bytes_storage", + expected: 32, + }, + { + name: "bytes type", + variableType: "t_bytes32", + expected: 32, + }, + { + name: "address type", + variableType: "t_address", + expected: 20, + }, + { + name: "bool type", + variableType: "t_bool", + expected: 1, + }, + { + name: "array type", + variableType: "t_array(t_uint256)2", + expected: 64, // 2 * 32 + }, + { + name: "unsupported type", + variableType: "t_unknown", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + length, err := parseVariableLength(tt.variableType, tt.types) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, length) + } + }) + } +} + +func Test_validateSpacer(t *testing.T) { + tests := []struct { + name string + variable solc.StorageLayoutEntry + types map[string]solc.StorageLayoutType + expectedErrs int + errorContains string + }{ + { + name: "valid spacer", + variable: solc.StorageLayoutEntry{ + Contract: "TestContract", + Label: "spacer_1_2_32", + Slot: 1, + Offset: 2, + Type: "t_uint256", + }, + types: map[string]solc.StorageLayoutType{ + "t_uint256": {NumberOfBytes: 32}, + }, + expectedErrs: 0, + }, + { + name: "invalid name format", + variable: solc.StorageLayoutEntry{ + Label: "spacer_invalid", + }, + expectedErrs: 1, + errorContains: "invalid spacer name format", + }, + { + name: "wrong slot", + variable: solc.StorageLayoutEntry{ + Contract: "TestContract", + Label: "spacer_1_2_32", + Slot: 2, + Offset: 2, + Type: "t_uint256", + }, + types: map[string]solc.StorageLayoutType{ + "t_uint256": {NumberOfBytes: 32}, + }, + expectedErrs: 1, + errorContains: "is in slot", + }, + { + name: "wrong offset", + variable: solc.StorageLayoutEntry{ + Contract: "TestContract", + Label: "spacer_1_2_32", + Slot: 1, + Offset: 3, + Type: "t_uint256", + }, + types: map[string]solc.StorageLayoutType{ + "t_uint256": {NumberOfBytes: 32}, + }, + expectedErrs: 1, + errorContains: "is at offset", + }, + { + name: "wrong length", + variable: solc.StorageLayoutEntry{ + Contract: "TestContract", + Label: "spacer_1_2_32", + Slot: 1, + Offset: 2, + Type: "t_uint128", + }, + types: map[string]solc.StorageLayoutType{ + "t_uint128": {NumberOfBytes: 16}, + }, + expectedErrs: 1, + errorContains: "bytes long", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errors := validateSpacer(tt.variable, tt.types) + require.Len(t, errors, tt.expectedErrs) + if tt.errorContains != "" { + require.Contains(t, errors[0].Error(), tt.errorContains) + } + }) + } +} diff --git a/packages/contracts-bedrock/scripts/checks/test-names/main.go b/packages/contracts-bedrock/scripts/checks/test-names/main.go new file mode 100644 index 0000000000000..84ead0d6fa0c6 --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/test-names/main.go @@ -0,0 +1,144 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "strings" + "unicode" + + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/scripts/checks/common" +) + +func main() { + if err := common.ProcessFilesGlob( + []string{"forge-artifacts/**/*.json"}, + []string{}, + processFile, + ); err != nil { + fmt.Printf("error: %v\n", err) + os.Exit(1) + } +} + +func processFile(path string) []error { + artifact, err := common.ReadForgeArtifact(path) + if err != nil { + return []error{err} + } + + var errors []error + names := extractTestNames(artifact) + for _, name := range names { + if err = checkTestName(name); err != nil { + errors = append(errors, err) + } + } + + return errors +} + +func extractTestNames(artifact *solc.ForgeArtifact) []string { + isTest := false + for _, entry := range artifact.Abi.Methods { + if entry.Name == "IS_TEST" { + isTest = true + break + } + } + if !isTest { + return nil + } + + names := []string{} + for _, entry := range artifact.Abi.Methods { + if !strings.HasPrefix(entry.Name, "test") { + continue + } + names = append(names, entry.Name) + } + + return names +} + +type CheckFunc func(parts []string) bool + +type CheckInfo struct { + error string + check CheckFunc +} + +var checks = map[string]CheckInfo{ + "doubleUnderscores": { + error: "test names cannot have double underscores", + check: func(parts []string) bool { + for _, part := range parts { + if len(strings.TrimSpace(part)) == 0 { + return false + } + } + return true + }, + }, + "camelCase": { + error: "test name parts should be in camelCase", + check: func(parts []string) bool { + for _, part := range parts { + if len(part) > 0 && unicode.IsUpper(rune(part[0])) { + return false + } + } + return true + }, + }, + "partsCount": { + error: "test names should have either 3 or 4 parts, each separated by underscores", + check: func(parts []string) bool { + return len(parts) == 3 || len(parts) == 4 + }, + }, + "prefix": { + error: "test names should begin with 'test', 'testFuzz', or 'testDiff'", + check: func(parts []string) bool { + return len(parts) > 0 && (parts[0] == "test" || parts[0] == "testFuzz" || parts[0] == "testDiff") + }, + }, + "suffix": { + error: "test names should end with either 'succeeds', 'reverts', 'fails', 'works', or 'benchmark[_num]'", + check: func(parts []string) bool { + if len(parts) == 0 { + return false + } + last := parts[len(parts)-1] + if last == "succeeds" || last == "reverts" || last == "fails" || last == "works" { + return true + } + if len(parts) >= 2 && parts[len(parts)-2] == "benchmark" { + _, err := strconv.Atoi(last) + return err == nil + } + return last == "benchmark" + }, + }, + "failureParts": { + error: "failure tests should have 4 parts, third part should indicate the reason for failure", + check: func(parts []string) bool { + if len(parts) == 0 { + return false + } + last := parts[len(parts)-1] + return len(parts) == 4 || (last != "reverts" && last != "fails") + }, + }, +} + +func checkTestName(name string) error { + parts := strings.Split(name, "_") + for _, check := range checks { + if !check.check(parts) { + return fmt.Errorf("%s: %s", name, check.error) + } + } + return nil +} diff --git a/packages/contracts-bedrock/scripts/checks/test-names/main_test.go b/packages/contracts-bedrock/scripts/checks/test-names/main_test.go new file mode 100644 index 0000000000000..5a9a0fd1846e0 --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/test-names/main_test.go @@ -0,0 +1,283 @@ +package main + +import ( + "reflect" + "slices" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" + "github.com/ethereum/go-ethereum/accounts/abi" +) + +func TestCamelCaseCheck(t *testing.T) { + tests := []struct { + name string + parts []string + expected bool + }{ + {"valid single part", []string{"test"}, true}, + {"valid multiple parts", []string{"test", "something", "succeeds"}, true}, + {"invalid uppercase", []string{"Test"}, false}, + {"invalid middle uppercase", []string{"test", "Something", "succeeds"}, false}, + {"empty parts", []string{}, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checks["camelCase"].check(tt.parts); got != tt.expected { + t.Errorf("checkCamelCase error for %v = %v, want %v", tt.parts, got, tt.expected) + } + }) + } +} + +func TestPartsCountCheck(t *testing.T) { + tests := []struct { + name string + parts []string + expected bool + }{ + {"three parts", []string{"test", "something", "succeeds"}, true}, + {"four parts", []string{"test", "something", "reason", "fails"}, true}, + {"too few parts", []string{"test", "fails"}, false}, + {"too many parts", []string{"test", "a", "b", "c", "fails"}, false}, + {"empty parts", []string{}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checks["partsCount"].check(tt.parts); got != tt.expected { + t.Errorf("checkPartsCount error for %v = %v, want %v", tt.parts, got, tt.expected) + } + }) + } +} + +func TestPrefixCheck(t *testing.T) { + tests := []struct { + name string + parts []string + expected bool + }{ + {"valid test", []string{"test", "something", "succeeds"}, true}, + {"valid testFuzz", []string{"testFuzz", "something", "succeeds"}, true}, + {"valid testDiff", []string{"testDiff", "something", "succeeds"}, true}, + {"invalid prefix", []string{"testing", "something", "succeeds"}, false}, + {"empty parts", []string{}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checks["prefix"].check(tt.parts); got != tt.expected { + t.Errorf("checkPrefix error for %v = %v, want %v", tt.parts, got, tt.expected) + } + }) + } +} + +func TestSuffixCheck(t *testing.T) { + tests := []struct { + name string + parts []string + expected bool + }{ + {"valid succeeds", []string{"test", "something", "succeeds"}, true}, + {"valid reverts", []string{"test", "something", "reverts"}, true}, + {"valid fails", []string{"test", "something", "fails"}, true}, + {"valid works", []string{"test", "something", "works"}, true}, + {"valid benchmark", []string{"test", "something", "benchmark"}, true}, + {"valid benchmark_num", []string{"test", "something", "benchmark", "123"}, true}, + {"invalid suffix", []string{"test", "something", "invalid"}, false}, + {"invalid benchmark_text", []string{"test", "something", "benchmark", "abc"}, false}, + {"empty parts", []string{}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checks["suffix"].check(tt.parts); got != tt.expected { + t.Errorf("checkSuffix error for %v = %v, want %v", tt.parts, got, tt.expected) + } + }) + } +} + +func TestFailurePartsCheck(t *testing.T) { + tests := []struct { + name string + parts []string + expected bool + }{ + {"valid failure with reason", []string{"test", "something", "reason", "fails"}, true}, + {"valid failure with reason", []string{"test", "something", "reason", "reverts"}, true}, + {"invalid failure without reason", []string{"test", "something", "fails"}, false}, + {"invalid failure without reason", []string{"test", "something", "reverts"}, false}, + {"valid non-failure with three parts", []string{"test", "something", "succeeds"}, true}, + {"empty parts", []string{}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checks["failureParts"].check(tt.parts); got != tt.expected { + t.Errorf("checkFailureParts error for %v = %v, want %v", tt.parts, got, tt.expected) + } + }) + } +} + +func TestCheckTestName(t *testing.T) { + tests := []struct { + name string + testName string + shouldSucceed bool + }{ + // Valid test names - Basic patterns + {"valid basic test succeeds", "test_something_succeeds", true}, + {"valid basic test fails with reason", "test_something_reason_fails", true}, + {"valid basic test reverts with reason", "test_something_reason_reverts", true}, + {"valid basic test works", "test_something_works", true}, + + // Valid test names - Fuzz variants + {"valid fuzz test succeeds", "testFuzz_something_succeeds", true}, + {"valid fuzz test fails with reason", "testFuzz_something_reason_fails", true}, + {"valid fuzz test reverts with reason", "testFuzz_something_reason_reverts", true}, + {"valid fuzz test works", "testFuzz_something_works", true}, + + // Valid test names - Diff variants + {"valid diff test succeeds", "testDiff_something_succeeds", true}, + {"valid diff test fails with reason", "testDiff_something_reason_fails", true}, + {"valid diff test reverts with reason", "testDiff_something_reason_reverts", true}, + {"valid diff test works", "testDiff_something_works", true}, + + // Valid test names - Benchmark variants + {"valid benchmark test", "test_something_benchmark", true}, + {"valid benchmark with number", "test_something_benchmark_123", true}, + {"valid benchmark with large number", "test_something_benchmark_999999", true}, + {"valid benchmark with zero", "test_something_benchmark_0", true}, + + // Valid test names - Complex middle parts + {"valid complex middle part", "test_complexOperation_succeeds", true}, + {"valid multiple word middle", "test_veryComplexOperation_succeeds", true}, + {"valid numbers in middle", "test_operation123_succeeds", true}, + {"valid special case", "test_specialCase_reason_fails", true}, + + // Invalid test names - Prefix issues + {"invalid empty string", "", false}, + {"invalid prefix Test", "Test_something_succeeds", false}, + {"invalid prefix testing", "testing_something_succeeds", false}, + {"invalid prefix testfuzz", "testfuzz_something_succeeds", false}, + {"invalid prefix testdiff", "testdiff_something_succeeds", false}, + {"invalid prefix TEST", "TEST_something_succeeds", false}, + + // Invalid test names - Suffix issues + {"invalid suffix succeed", "test_something_succeed", false}, + {"invalid suffix revert", "test_something_revert", false}, + {"invalid suffix fail", "test_something_fail", false}, + {"invalid suffix work", "test_something_work", false}, + {"invalid suffix benchmarks", "test_something_benchmarks", false}, + {"invalid benchmark suffix text", "test_something_benchmark_abc", false}, + {"invalid benchmark suffix special", "test_something_benchmark_123abc", false}, + + // Invalid test names - Case issues + {"invalid uppercase middle", "test_Something_succeeds", false}, + {"invalid multiple uppercase", "test_SomethingHere_succeeds", false}, + {"invalid all caps middle", "test_SOMETHING_succeeds", false}, + {"invalid mixed case suffix", "test_something_Succeeds", false}, + + // Invalid test names - Structure issues + {"invalid single part", "test", false}, + {"invalid two parts", "test_succeeds", false}, + {"invalid five parts", "test_this_that_those_succeeds", false}, + {"invalid six parts", "test_this_that_those_these_succeeds", false}, + {"invalid failure without reason", "test_something_fails", false}, + {"invalid revert without reason", "test_something_reverts", false}, + + // Invalid test names - Special cases + {"invalid empty parts", "test__succeeds", false}, + {"invalid multiple underscores", "test___succeeds", false}, + {"invalid trailing underscore", "test_something_succeeds_", false}, + {"invalid leading underscore", "_test_something_succeeds", false}, + {"invalid benchmark no number", "test_something_benchmark_", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkTestName(tt.testName) + if (err != nil) == tt.shouldSucceed { + t.Errorf("checkTestName(%q) error = %v, shouldSucceed %v", tt.testName, err, tt.shouldSucceed) + } + }) + } +} + +func TestExtractTestNames(t *testing.T) { + tests := []struct { + name string + artifact *solc.ForgeArtifact + want []string + }{ + { + name: "valid test contract", + artifact: &solc.ForgeArtifact{ + Abi: abi.ABI{ + Methods: map[string]abi.Method{ + "IS_TEST": {Name: "IS_TEST"}, + "test_something_succeeds": {Name: "test_something_succeeds"}, + "test_other_fails": {Name: "test_other_fails"}, + "not_a_test": {Name: "not_a_test"}, + "testFuzz_something_works": {Name: "testFuzz_something_works"}, + }, + }, + }, + want: []string{ + "test_something_succeeds", + "test_other_fails", + "testFuzz_something_works", + }, + }, + { + name: "non-test contract", + artifact: &solc.ForgeArtifact{ + Abi: abi.ABI{ + Methods: map[string]abi.Method{ + "test_something_succeeds": {Name: "test_something_succeeds"}, + "not_a_test": {Name: "not_a_test"}, + }, + }, + }, + want: nil, + }, + { + name: "empty contract", + artifact: &solc.ForgeArtifact{ + Abi: abi.ABI{ + Methods: map[string]abi.Method{}, + }, + }, + want: nil, + }, + { + name: "test contract with no test methods", + artifact: &solc.ForgeArtifact{ + Abi: abi.ABI{ + Methods: map[string]abi.Method{ + "IS_TEST": {Name: "IS_TEST"}, + "not_a_test": {Name: "not_a_test"}, + "another_method": {Name: "another_method"}, + }, + }, + }, + want: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := extractTestNames(tt.artifact) + slices.Sort(got) + slices.Sort(tt.want) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("extractTestNames() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/packages/contracts-bedrock/scripts/checks/unused-imports/main.go b/packages/contracts-bedrock/scripts/checks/unused-imports/main.go index 9fc901b4326e2..df3ab4f4494de 100644 --- a/packages/contracts-bedrock/scripts/checks/unused-imports/main.go +++ b/packages/contracts-bedrock/scripts/checks/unused-imports/main.go @@ -2,101 +2,51 @@ package main import ( "bufio" - "errors" "fmt" "os" - "path/filepath" "regexp" - "runtime" "strings" - "sync" - "sync/atomic" + + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/scripts/checks/common" ) var importPattern = regexp.MustCompile(`import\s*{([^}]+)}`) var asPattern = regexp.MustCompile(`(\S+)\s+as\s+(\S+)`) func main() { - if err := run(); err != nil { - writeStderr("an error occurred: %v", err) + if err := common.ProcessFilesGlob( + []string{"src/**/*.sol", "scripts/**/*.sol", "test/**/*.sol"}, + []string{}, + processFile, + ); err != nil { + fmt.Printf("error: %v\n", err) os.Exit(1) } } -func writeStderr(msg string, args ...any) { - _, _ = fmt.Fprintf(os.Stderr, msg+"\n", args...) -} - -func run() error { - cwd, err := os.Getwd() - if err != nil { - return fmt.Errorf("failed to get current working directory: %w", err) - } - - var hasErr int32 - var outMtx sync.Mutex - fail := func(msg string, args ...any) { - outMtx.Lock() - writeStderr("❌ "+msg, args...) - outMtx.Unlock() - atomic.StoreInt32(&hasErr, 1) - } - - dirs := []string{"src", "scripts", "test"} - sem := make(chan struct{}, runtime.NumCPU()) - - for _, dir := range dirs { - dirPath := filepath.Join(cwd, dir) - if _, err := os.Stat(dirPath); errors.Is(err, os.ErrNotExist) { - continue - } - - err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() && strings.HasSuffix(info.Name(), ".sol") { - sem <- struct{}{} - go func() { - defer func() { <-sem }() - processFile(path, fail) - }() - } - return nil - }) - - if err != nil { - return fmt.Errorf("failed to walk directory %s: %w", dir, err) - } - } - - for i := 0; i < cap(sem); i++ { - sem <- struct{}{} - } - - if atomic.LoadInt32(&hasErr) == 1 { - return errors.New("unused imports check failed, see logs above") - } - - return nil -} - -func processFile(filePath string, fail func(string, ...any)) { +func processFile(filePath string) []error { content, err := os.ReadFile(filePath) if err != nil { - fail("%s: failed to read file: %v", filePath, err) - return + return []error{fmt.Errorf("%s: failed to read file: %w", filePath, err)} } imports := findImports(string(content)) - unusedImports := checkUnusedImports(imports, string(content)) + var unusedImports []string + for _, imp := range imports { + if !isImportUsed(imp, string(content)) { + unusedImports = append(unusedImports, imp) + } + } if len(unusedImports) > 0 { - fail("File: %s\nUnused imports:", filePath) + var errors []error for _, unused := range unusedImports { - fail(" - %s", unused) + errors = append(errors, fmt.Errorf("%s", unused)) } + return errors } + + return nil } func findImports(content string) []string { @@ -106,31 +56,19 @@ func findImports(content string) []string { if len(match) > 1 { importList := strings.Split(match[1], ",") for _, imp := range importList { - imports = append(imports, strings.TrimSpace(imp)) + imp = strings.TrimSpace(imp) + if asMatch := asPattern.FindStringSubmatch(imp); len(asMatch) > 2 { + // Use the renamed identifier (after 'as') + imports = append(imports, strings.TrimSpace(asMatch[2])) + } else { + imports = append(imports, imp) + } } } } return imports } -func checkUnusedImports(imports []string, content string) []string { - var unusedImports []string - for _, imp := range imports { - searchTerm := imp - displayName := imp - - if match := asPattern.FindStringSubmatch(imp); len(match) > 2 { - searchTerm = match[2] - displayName = fmt.Sprintf("%s as %s", match[1], match[2]) - } - - if !isImportUsed(searchTerm, content) { - unusedImports = append(unusedImports, displayName) - } - } - return unusedImports -} - func isImportUsed(imp, content string) bool { scanner := bufio.NewScanner(strings.NewReader(content)) for scanner.Scan() { diff --git a/packages/contracts-bedrock/scripts/checks/unused-imports/main_test.go b/packages/contracts-bedrock/scripts/checks/unused-imports/main_test.go new file mode 100644 index 0000000000000..7d03867a8a40b --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/unused-imports/main_test.go @@ -0,0 +1,131 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_findImports(t *testing.T) { + tests := []struct { + name string + content string + expected []string + }{ + { + name: "finds single named import", + content: ` + pragma solidity ^0.8.0; + import { Contract } from "./Contract.sol"; + contract Test {} + `, + expected: []string{"Contract"}, + }, + { + name: "finds multiple named imports", + content: ` + pragma solidity ^0.8.0; + import { Contract1, Contract2 } from "./Contracts.sol"; + contract Test {} + `, + expected: []string{"Contract1", "Contract2"}, + }, + { + name: "handles import with as keyword", + content: ` + pragma solidity ^0.8.0; + import { Contract as Renamed } from "./Contract.sol"; + contract Test {} + `, + expected: []string{"Renamed"}, + }, + { + name: "handles multiple imports with as keyword", + content: ` + pragma solidity ^0.8.0; + import { Contract1 as C1, Contract2 as C2 } from "./Contracts.sol"; + contract Test {} + `, + expected: []string{"C1", "C2"}, + }, + { + name: "ignores regular imports", + content: ` + pragma solidity ^0.8.0; + import "./Contract.sol"; + contract Test {} + `, + expected: nil, + }, + { + name: "empty content", + content: "", + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := findImports(tt.content) + require.Equal(t, tt.expected, result) + }) + } +} + +func Test_isImportUsed(t *testing.T) { + tests := []struct { + name string + importedName string + content string + expected bool + }{ + { + name: "import used in contract", + importedName: "UsedContract", + content: ` + contract Test { + UsedContract used; + } + `, + expected: true, + }, + { + name: "import used in inheritance", + importedName: "BaseContract", + content: ` + contract Test is BaseContract { + } + `, + expected: true, + }, + { + name: "import used in function", + importedName: "Utility", + content: ` + contract Test { + function test() { + Utility.doSomething(); + } + } + `, + expected: true, + }, + { + name: "import not used", + importedName: "UnusedContract", + content: ` + contract Test { + OtherContract other; + } + `, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isImportUsed(tt.importedName, tt.content) + require.Equal(t, tt.expected, result) + }) + } +} diff --git a/packages/contracts-bedrock/scripts/utils/BaseDeployIO.sol b/packages/contracts-bedrock/scripts/deploy/BaseDeployIO.sol similarity index 100% rename from packages/contracts-bedrock/scripts/utils/BaseDeployIO.sol rename to packages/contracts-bedrock/scripts/deploy/BaseDeployIO.sol diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 25d67be3828c8..cecf2b9427317 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -7,7 +7,7 @@ import { console2 as console } from "forge-std/console2.sol"; // Scripts import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; -import { ISystemConfigInterop } from "src/L1/interfaces/ISystemConfigInterop.sol"; +import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; @@ -15,21 +15,21 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { Types } from "scripts/libraries/Types.sol"; // Interfaces -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { ProtocolVersion, IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; -import { IMIPS } from "src/cannon/interfaces/IMIPS.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; +import { ProtocolVersion, IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { IMIPS } from "interfaces/cannon/IMIPS.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; library ChainAssertions { diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 5c29517292c33..63dc1cd6b24fa 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -12,56 +12,46 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Deployer } from "scripts/deploy/Deployer.sol"; import { Chains } from "scripts/libraries/Chains.sol"; import { Config } from "scripts/libraries/Config.sol"; -import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; +import { StateDiff } from "scripts/libraries/StateDiff.sol"; import { Process } from "scripts/libraries/Process.sol"; import { ChainAssertions } from "scripts/deploy/ChainAssertions.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; -import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/DeploySuperchain.s.sol"; +import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/deploy/DeploySuperchain.s.sol"; import { DeployImplementationsInput, DeployImplementations, DeployImplementationsInterop, DeployImplementationsOutput -} from "scripts/DeployImplementations.s.sol"; +} from "scripts/deploy/DeployImplementations.s.sol"; // Contracts -import { AddressManager } from "src/legacy/AddressManager.sol"; -import { StorageSetter } from "src/universal/StorageSetter.sol"; +import { OPContractsManager } from "src/L1/OPContractsManager.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; import { Types } from "scripts/libraries/Types.sol"; import { Duration } from "src/dispute/lib/LibUDT.sol"; -import "src/dispute/lib/Types.sol"; +import { StorageSlot, ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; +import { GameType, Claim, GameTypes, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; // Interfaces -import { IProxy } from "src/universal/interfaces/IProxy.sol"; -import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; -import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IPermissionedDisputeGame } from "src/dispute/interfaces/IPermissionedDisputeGame.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IMIPS } from "src/cannon/interfaces/IMIPS.sol"; -import { IMIPS2 } from "src/cannon/interfaces/IMIPS2.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IL1ChugSplashProxy } from "src/legacy/interfaces/IL1ChugSplashProxy.sol"; -import { IResolvedDelegateProxy } from "src/legacy/interfaces/IResolvedDelegateProxy.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; +import { ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IMIPS } from "interfaces/cannon/IMIPS.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; /// @title Deploy /// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function. @@ -74,20 +64,6 @@ import { IResolvedDelegateProxy } from "src/legacy/interfaces/IResolvedDelegateP contract Deploy is Deployer { using stdJson for string; - /// @notice FaultDisputeGameParams is a struct that contains the parameters necessary to call - /// the function _setFaultGameImplementation. This struct exists because the EVM needs - /// to finally adopt PUSHN and get rid of stack too deep once and for all. - /// Someday we will look back and laugh about stack too deep, today is not that day. - struct FaultDisputeGameParams { - IAnchorStateRegistry anchorStateRegistry; - IDelayedWETH weth; - GameType gameType; - Claim absolutePrestate; - IBigStepper faultVm; - uint256 maxGameDepth; - Duration maxClockDuration; - } - //////////////////////////////////////////////////////////////// // Modifiers // //////////////////////////////////////////////////////////////// @@ -107,18 +83,6 @@ contract Deploy is Deployer { } } - /// @notice Modifier that will only allow a function to be called on a public - /// testnet or devnet. - modifier onlyTestnetOrDevnet() { - uint256 chainid = block.chainid; - if ( - chainid == Chains.Goerli || chainid == Chains.Sepolia || chainid == Chains.LocalDevnet - || chainid == Chains.GethDevnet - ) { - _; - } - } - /// @notice Modifier that wraps a function with statediff recording. /// The returned AccountAccess[] array is then written to /// the `snapshots/state-diff/.json` output file. @@ -131,7 +95,7 @@ contract Deploy is Deployer { accesses.length, vm.toString(block.chainid) ); - string memory json = LibStateDiff.encodeAccountAccesses(accesses); + string memory json = StateDiff.encodeAccountAccesses(accesses); string memory statediffPath = string.concat(vm.projectRoot(), "/snapshots/state-diff/", vm.toString(block.chainid), ".json"); vm.writeJson({ json: json, path: statediffPath }); @@ -165,7 +129,7 @@ contract Deploy is Deployer { L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), ProtocolVersions: getAddress("ProtocolVersionsProxy"), SuperchainConfig: getAddress("SuperchainConfigProxy"), - OPContractsManager: getAddress("OPContractsManagerProxy") + OPContractsManager: getAddress("OPContractsManager") }); } @@ -190,28 +154,6 @@ contract Deploy is Deployer { }); } - //////////////////////////////////////////////////////////////// - // State Changing Helper Functions // - //////////////////////////////////////////////////////////////// - - /// @notice Transfer ownership of the ProxyAdmin contract to the final system owner - function transferProxyAdminOwnership() public broadcast { - // Get the ProxyAdmin contract. - IProxyAdmin proxyAdmin = IProxyAdmin(mustGetAddress("ProxyAdmin")); - - // Transfer ownership to the final system owner if necessary. - address owner = proxyAdmin.owner(); - address finalSystemOwner = cfg.finalSystemOwner(); - if (owner != finalSystemOwner) { - proxyAdmin.transferOwnership(finalSystemOwner); - console.log("ProxyAdmin ownership transferred to final system owner at: %s", finalSystemOwner); - } - - // Make sure the ProxyAdmin owner is set to the final system owner. - owner = proxyAdmin.owner(); - require(owner == finalSystemOwner, "Deploy: ProxyAdmin ownership not transferred to final system owner"); - } - //////////////////////////////////////////////////////////////// // SetUp and Run // //////////////////////////////////////////////////////////////// @@ -219,20 +161,13 @@ contract Deploy is Deployer { /// @notice Deploy all of the L1 contracts necessary for a full Superchain with a single Op Chain. function run() public { console.log("Deploying a fresh OP Stack including SuperchainConfig"); - _run(); + _run({ _needsSuperchain: true }); } /// @notice Deploy a new OP Chain using an existing SuperchainConfig and ProtocolVersions /// @param _superchainConfigProxy Address of the existing SuperchainConfig proxy /// @param _protocolVersionsProxy Address of the existing ProtocolVersions proxy - /// @param _includeDump Whether to include a state dump after deployment - function runWithSuperchain( - address payable _superchainConfigProxy, - address payable _protocolVersionsProxy, - bool _includeDump - ) - public - { + function runWithSuperchain(address payable _superchainConfigProxy, address payable _protocolVersionsProxy) public { require(_superchainConfigProxy != address(0), "Deploy: must specify address for superchain config proxy"); require(_protocolVersionsProxy != address(0), "Deploy: must specify address for protocol versions proxy"); @@ -250,16 +185,13 @@ contract Deploy is Deployer { save("ProtocolVersions", pvProxy.implementation()); save("ProtocolVersionsProxy", _protocolVersionsProxy); - _run(false); - - if (_includeDump) { - vm.dumpState(Config.stateDumpPath("")); - } + _run({ _needsSuperchain: false }); } + /// @notice Used for L1 alloc generation. function runWithStateDump() public { vm.chainId(cfg.l1ChainID()); - _run(); + _run({ _needsSuperchain: true }); vm.dumpState(Config.stateDumpPath("")); } @@ -286,24 +218,19 @@ contract Deploy is Deployer { deployAnchorStateRegistry(); initializeAnchorStateRegistry(); - setAlphabetFaultGameImplementation({ _allowUpgrade: true }); - setFastFaultGameImplementation({ _allowUpgrade: true }); - setCannonFaultGameImplementation({ _allowUpgrade: true }); - setPermissionedCannonFaultGameImplementation({ _allowUpgrade: true }); + setAlphabetFaultGameImplementation(); + setFastFaultGameImplementation(); + setCannonFaultGameImplementation(); } /// @notice Deploy all L1 contracts and write the state diff to a file. + /// Used to generate kontrol tests. function runWithStateDiff() public stateDiff { - _run(); - } - - /// @notice Compatibility function for tests that override _run(). - function _run() internal virtual { - _run(true); + _run({ _needsSuperchain: true }); } /// @notice Internal function containing the deploy logic. - function _run(bool _needsSuperchain) internal { + function _run(bool _needsSuperchain) internal virtual { console.log("start of L1 Deploy!"); // Set up the Superchain if needed. @@ -316,11 +243,36 @@ contract Deploy is Deployer { // Deploy Current OPChain Contracts deployOpChain(); + // Apply modifications for non-standard configurations not supported by the OPCM deployment + if (cfg.useFaultProofs()) { + vm.startPrank(ISuperchainConfig(mustGetAddress("SuperchainConfigProxy")).guardian()); + IOptimismPortal2(mustGetAddress("OptimismPortalProxy")).setRespectedGameType( + GameType.wrap(uint32(cfg.respectedGameType())) + ); + vm.stopPrank(); + } else { + // The L2OutputOracle is not deployed by the OPCM, we deploy the proxy and initialize it here. + deployERC1967Proxy("L2OutputOracleProxy"); + initializeL2OutputOracle(); + + // The OptimismPortalProxy contract is used both with and without Fault Proofs enabled, and is deployed by + // deployOPChain. If Fault Proofs are disabled, then we need to reinitialize the OptimismPortalProxy + // as the legacy OptimismPortal. + resetInitializedProxy("OptimismPortal"); + initializeOptimismPortal(); + } + + if (cfg.useCustomGasToken()) { + // Reset the systemconfig then reinitialize it with the custom gas token + resetInitializedProxy("SystemConfig"); + initializeSystemConfig(); + } + if (cfg.useAltDA()) { bytes32 typeHash = keccak256(bytes(cfg.daCommitmentType())); bytes32 keccakHash = keccak256(bytes("KeccakCommitment")); if (typeHash == keccakHash) { - setupOpAltDA(); + deployOpAltDA(); } } @@ -387,13 +339,13 @@ contract Deploy is Deployer { dii.set(dii.disputeGameFinalityDelaySeconds.selector, cfg.disputeGameFinalityDelaySeconds()); dii.set(dii.mipsVersion.selector, Config.useMultithreadedCannon() ? 2 : 1); string memory release = "dev"; - dii.set(dii.release.selector, release); + dii.set(dii.l1ContractsRelease.selector, release); dii.set( dii.standardVersionsToml.selector, string.concat(vm.projectRoot(), "/test/fixtures/standard-versions.toml") ); dii.set(dii.superchainConfigProxy.selector, mustGetAddress("SuperchainConfigProxy")); dii.set(dii.protocolVersionsProxy.selector, mustGetAddress("ProtocolVersionsProxy")); - dii.set(dii.opcmProxyOwner.selector, cfg.finalSystemOwner()); + dii.set(dii.salt.selector, _implSalt()); if (_isInterop) { di = DeployImplementations(new DeployImplementationsInterop()); @@ -418,8 +370,7 @@ contract Deploy is Deployer { save("DelayedWETH", address(dio.delayedWETHImpl())); save("PreimageOracle", address(dio.preimageOracleSingleton())); save("Mips", address(dio.mipsSingleton())); - save("OPContractsManagerProxy", address(dio.opcmProxy())); - save("OPContractsManager", address(dio.opcmImpl())); + save("OPContractsManager", address(dio.opcm())); Types.ContractSet memory contracts = _impls(); ChainAssertions.checkL1CrossDomainMessenger({ _contracts: contracts, _vm: vm, _isProxy: false }); @@ -452,169 +403,70 @@ contract Deploy is Deployer { /// @notice Deploy all of the OP Chain specific contracts function deployOpChain() public { console.log("Deploying OP Chain"); - deployAddressManager(); - deployProxyAdmin(); - transferAddressManagerOwnership(); // to the ProxyAdmin // Ensure that the requisite contracts are deployed - mustGetAddress("SuperchainConfigProxy"); - mustGetAddress("AddressManager"); - mustGetAddress("ProxyAdmin"); - - deployERC1967Proxy("OptimismPortalProxy"); - deployERC1967Proxy("SystemConfigProxy"); - deployL1StandardBridgeProxy(); - deployL1CrossDomainMessengerProxy(); - deployERC1967Proxy("OptimismMintableERC20FactoryProxy"); - deployERC1967Proxy("L1ERC721BridgeProxy"); - - // Both the DisputeGameFactory and L2OutputOracle proxies are deployed regardless of whether fault proofs is - // enabled to prevent a nastier refactor to the deploy scripts. In the future, the L2OutputOracle will be - // removed. If fault proofs are not enabled, the DisputeGameFactory proxy will be unused. - deployERC1967Proxy("DisputeGameFactoryProxy"); - deployERC1967Proxy("DelayedWETHProxy"); - deployERC1967Proxy("PermissionedDelayedWETHProxy"); - deployERC1967Proxy("AnchorStateRegistryProxy"); - - deployAnchorStateRegistry(); - - // Deploy and setup the legacy (pre-faultproofs) contracts - if (!cfg.useFaultProofs()) { - deployERC1967Proxy("L2OutputOracleProxy"); - } + address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); + OPContractsManager opcm = OPContractsManager(mustGetAddress("OPContractsManager")); + + OPContractsManager.DeployInput memory deployInput = getDeployInput(); + OPContractsManager.DeployOutput memory deployOutput = opcm.deploy(deployInput); + + // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct + save("ProxyAdmin", address(deployOutput.opChainProxyAdmin)); + save("AddressManager", address(deployOutput.addressManager)); + save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721BridgeProxy)); + save("SystemConfigProxy", address(deployOutput.systemConfigProxy)); + save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); + save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); + save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); + + // Fault Proof contracts + save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); + save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); + save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); + save("AnchorStateRegistry", address(deployOutput.anchorStateRegistryImpl)); + save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame)); + save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy)); + + // Check if the permissionless game implementation is already set + IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); + address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); - initializeOpChain(); + // Deploy and setup the PermissionlessDelayedWeth not provided by the OPCM. + // If the following require statement is hit, you can delete the block of code after it. + require( + permissionlessGameImpl == address(0), + "Deploy: The PermissionlessDelayedWETH is already set by the OPCM, it is no longer necessary to deploy it separately." + ); + address delayedWETHImpl = mustGetAddress("DelayedWETH"); + address delayedWETHPermissionlessGameProxy = deployERC1967ProxyWithOwner("DelayedWETHProxy", msg.sender); + vm.broadcast(msg.sender); + IProxy(payable(delayedWETHPermissionlessGameProxy)).upgradeToAndCall({ + _implementation: delayedWETHImpl, + _data: abi.encodeCall(IDelayedWETH.initialize, (msg.sender, ISuperchainConfig(superchainConfigProxy))) + }); - setAlphabetFaultGameImplementation({ _allowUpgrade: false }); - setFastFaultGameImplementation({ _allowUpgrade: false }); - setCannonFaultGameImplementation({ _allowUpgrade: false }); - setPermissionedCannonFaultGameImplementation({ _allowUpgrade: false }); + setAlphabetFaultGameImplementation(); + setFastFaultGameImplementation(); + setCannonFaultGameImplementation(); transferDisputeGameFactoryOwnership(); transferDelayedWETHOwnership(); - } - - /// @notice Initialize all of the proxies in an OP Chain by upgrading to the correct proxy and calling the - /// initialize function - function initializeOpChain() public { - console.log("Initializing Op Chain proxies"); - - initializeOptimismPortal(); - initializeSystemConfig(); - initializeL1StandardBridge(); - initializeL1ERC721Bridge(); - initializeOptimismMintableERC20Factory(); - initializeL1CrossDomainMessenger(); - initializeDisputeGameFactory(); - initializeDelayedWETH(); - initializePermissionedDelayedWETH(); - initializeAnchorStateRegistry(); - - if (!cfg.useFaultProofs()) { - initializeL2OutputOracle(); - } + transferPermissionedDelayedWETHOwnership(); } /// @notice Add AltDA setup to the OP chain - function setupOpAltDA() public { + function deployOpAltDA() public { console.log("Deploying OP AltDA"); deployDataAvailabilityChallengeProxy(); deployDataAvailabilityChallenge(); initializeDataAvailabilityChallenge(); } - //////////////////////////////////////////////////////////////// - // Non-Proxied Deployment Functions // - //////////////////////////////////////////////////////////////// - - /// @notice Deploy the AddressManager - function deployAddressManager() public broadcast returns (address addr_) { - console.log("Deploying AddressManager"); - AddressManager manager = new AddressManager(); - require(manager.owner() == msg.sender); - - save("AddressManager", address(manager)); - console.log("AddressManager deployed at %s", address(manager)); - addr_ = address(manager); - } - - /// @notice Deploys the ProxyAdmin contract. Should NOT be used for the Superchain. - function deployProxyAdmin() public broadcast returns (address addr_) { - // Deploy the ProxyAdmin contract. - IProxyAdmin admin = IProxyAdmin( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "ProxyAdmin", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (msg.sender))) - }) - ); - - // Make sure the owner was set to the deployer. - require(admin.owner() == msg.sender); - - // Set the address manager if it is not already set. - IAddressManager addressManager = IAddressManager(mustGetAddress("AddressManager")); - if (admin.addressManager() != addressManager) { - admin.setAddressManager(addressManager); - } - - // Make sure the address manager is set properly. - require(admin.addressManager() == addressManager); - - // Return the address of the deployed contract. - addr_ = address(admin); - } - - /// @notice Deploy the StorageSetter contract, used for upgrades. - function deployStorageSetter() public broadcast returns (address addr_) { - console.log("Deploying StorageSetter"); - StorageSetter setter = new StorageSetter{ salt: _implSalt() }(); - console.log("StorageSetter deployed at: %s", address(setter)); - string memory version = setter.version(); - console.log("StorageSetter version: %s", version); - addr_ = address(setter); - } - //////////////////////////////////////////////////////////////// // Proxy Deployment Functions // //////////////////////////////////////////////////////////////// - /// @notice Deploy the L1StandardBridgeProxy using a ChugSplashProxy - function deployL1StandardBridgeProxy() public broadcast returns (address addr_) { - address proxyAdmin = mustGetAddress("ProxyAdmin"); - IL1ChugSplashProxy proxy = IL1ChugSplashProxy( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "L1ChugSplashProxy", - _nick: "L1StandardBridgeProxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ChugSplashProxy.__constructor__, (proxyAdmin))) - }) - ); - require(EIP1967Helper.getAdmin(address(proxy)) == proxyAdmin); - addr_ = address(proxy); - } - - /// @notice Deploy the L1CrossDomainMessengerProxy using a ResolvedDelegateProxy - function deployL1CrossDomainMessengerProxy() public broadcast returns (address addr_) { - IResolvedDelegateProxy proxy = IResolvedDelegateProxy( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "ResolvedDelegateProxy", - _nick: "L1CrossDomainMessengerProxy", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IResolvedDelegateProxy.__constructor__, - (IAddressManager(mustGetAddress("AddressManager")), "OVM_L1CrossDomainMessenger") - ) - ) - }) - ); - addr_ = address(proxy); - } - /// @notice Deploys an ERC1967Proxy contract with the ProxyAdmin as the owner. /// @param _name The name of the proxy contract to be deployed. /// @return addr_ The address of the deployed proxy contract. @@ -669,50 +521,22 @@ contract Deploy is Deployer { /// @notice Deploy the OptimismPortal function deployOptimismPortal() public broadcast returns (address addr_) { - if (cfg.useFaultProofs()) { - // Could also verify this inside DeployConfig but doing it here is a bit more reliable. - require( - uint32(cfg.respectedGameType()) == cfg.respectedGameType(), - "Deploy: respectedGameType must fit into uint32" - ); + require(!cfg.useFaultProofs(), "Deploy: FaultProofs OptimismPortal is deployed by OPCM"); + require(!cfg.useInterop(), "Deploy: The legacy OptimismPortal does not support interop"); - addr_ = DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "OptimismPortal2", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOptimismPortal2.__constructor__, - (cfg.proofMaturityDelaySeconds(), cfg.disputeGameFinalityDelaySeconds()) - ) - ) - }); - - // Override the `OptimismPortal2` contract to the deployed implementation. This is necessary - // to check the `OptimismPortal2` implementation alongside dependent contracts, which - // are always proxies. - Types.ContractSet memory contracts = _proxies(); - contracts.OptimismPortal2 = addr_; - ChainAssertions.checkOptimismPortal2({ _contracts: contracts, _cfg: cfg, _isProxy: false }); - } else { - if (cfg.useInterop()) { - console.log("Attempting to deploy OptimismPortal with interop, this config is a noop"); - } + addr_ = DeployUtils.create2AndSave({ + _save: this, + _salt: _implSalt(), + _name: "OptimismPortal", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal.__constructor__, ())) + }); - addr_ = DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "OptimismPortal", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal.__constructor__, ())) - }); - - // Override the `OptimismPortal` contract to the deployed implementation. This is necessary - // to check the `OptimismPortal` implementation alongside dependent contracts, which - // are always proxies. - Types.ContractSet memory contracts = _proxies(); - contracts.OptimismPortal = addr_; - ChainAssertions.checkOptimismPortal({ _contracts: contracts, _cfg: cfg, _isProxy: false }); - } + // Override the `OptimismPortal` contract to the deployed implementation. This is necessary + // to check the `OptimismPortal` implementation alongside dependent contracts, which + // are always proxies. + Types.ContractSet memory contracts = _proxies(); + contracts.OptimismPortal = addr_; + ChainAssertions.checkOptimismPortal({ _contracts: contracts, _cfg: cfg, _isProxy: false }); } /// @notice Deploy the L2OutputOracle @@ -741,19 +565,6 @@ contract Deploy is Deployer { addr_ = address(oracle); } - /// @notice Deploy Mips VM. Deploys either MIPS or MIPS2 depending on the environment - function deployMips() public broadcast returns (address addr_) { - addr_ = DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: Config.useMultithreadedCannon() ? "MIPS2" : "MIPS", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IMIPS2.__constructor__, (IPreimageOracle(mustGetAddress("PreimageOracle")))) - ) - }); - save("Mips", address(addr_)); - } - /// @notice Deploy the AnchorStateRegistry function deployAnchorStateRegistry() public broadcast returns (address addr_) { IAnchorStateRegistry anchorStateRegistry = IAnchorStateRegistry( @@ -773,63 +584,6 @@ contract Deploy is Deployer { addr_ = address(anchorStateRegistry); } - /// @notice Deploy the L1StandardBridge - function deployL1StandardBridge() public broadcast returns (address addr_) { - IL1StandardBridge bridge = IL1StandardBridge( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "L1StandardBridge", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1StandardBridge.__constructor__, ())) - }) - ); - - // Override the `L1StandardBridge` contract to the deployed implementation. This is necessary - // to check the `L1StandardBridge` implementation alongside dependent contracts, which - // are always proxies. - Types.ContractSet memory contracts = _proxies(); - contracts.L1StandardBridge = address(bridge); - ChainAssertions.checkL1StandardBridge({ _contracts: contracts, _isProxy: false }); - - addr_ = address(bridge); - } - - /// @notice Deploy the L1ERC721Bridge - function deployL1ERC721Bridge() public broadcast returns (address addr_) { - IL1ERC721Bridge bridge = IL1ERC721Bridge( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "L1ERC721Bridge", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ERC721Bridge.__constructor__, ())) - }) - ); - - // Override the `L1ERC721Bridge` contract to the deployed implementation. This is necessary - // to check the `L1ERC721Bridge` implementation alongside dependent contracts, which - // are always proxies. - Types.ContractSet memory contracts = _proxies(); - contracts.L1ERC721Bridge = address(bridge); - - ChainAssertions.checkL1ERC721Bridge({ _contracts: contracts, _isProxy: false }); - - addr_ = address(bridge); - } - - /// @notice Transfer ownership of the address manager to the ProxyAdmin - function transferAddressManagerOwnership() public broadcast { - console.log("Transferring AddressManager ownership to IProxyAdmin"); - IAddressManager addressManager = IAddressManager(mustGetAddress("AddressManager")); - address owner = addressManager.owner(); - address proxyAdmin = mustGetAddress("ProxyAdmin"); - if (owner != proxyAdmin) { - addressManager.transferOwnership(proxyAdmin); - console.log("AddressManager ownership transferred to %s", proxyAdmin); - } - - require(addressManager.owner() == proxyAdmin); - } - /// @notice Deploy the DataAvailabilityChallenge function deployDataAvailabilityChallenge() public broadcast returns (address addr_) { IDataAvailabilityChallenge dac = IDataAvailabilityChallenge( @@ -1020,133 +774,6 @@ contract Deploy is Deployer { ChainAssertions.checkSystemConfig({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); } - /// @notice Initialize the L1StandardBridge - function initializeL1StandardBridge() public broadcast { - console.log("Upgrading and initializing L1StandardBridge proxy"); - IProxyAdmin proxyAdmin = IProxyAdmin(mustGetAddress("ProxyAdmin")); - address l1StandardBridgeProxy = mustGetAddress("L1StandardBridgeProxy"); - address l1StandardBridge = mustGetAddress("L1StandardBridge"); - address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); - address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); - address systemConfigProxy = mustGetAddress("SystemConfigProxy"); - - uint256 proxyType = uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)); - if (proxyType != uint256(IProxyAdmin.ProxyType.CHUGSPLASH)) { - proxyAdmin.setProxyType(l1StandardBridgeProxy, IProxyAdmin.ProxyType.CHUGSPLASH); - } - require(uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)) == uint256(IProxyAdmin.ProxyType.CHUGSPLASH)); - - proxyAdmin.upgradeAndCall({ - _proxy: payable(l1StandardBridgeProxy), - _implementation: l1StandardBridge, - _data: abi.encodeCall( - IL1StandardBridge.initialize, - ( - ICrossDomainMessenger(l1CrossDomainMessengerProxy), - ISuperchainConfig(superchainConfigProxy), - ISystemConfig(systemConfigProxy) - ) - ) - }); - - string memory version = IL1StandardBridge(payable(l1StandardBridgeProxy)).version(); - console.log("L1StandardBridge version: %s", version); - - ChainAssertions.checkL1StandardBridge({ _contracts: _proxies(), _isProxy: true }); - } - - /// @notice Initialize the L1ERC721Bridge - function initializeL1ERC721Bridge() public broadcast { - console.log("Upgrading and initializing L1ERC721Bridge proxy"); - address l1ERC721BridgeProxy = mustGetAddress("L1ERC721BridgeProxy"); - address l1ERC721Bridge = mustGetAddress("L1ERC721Bridge"); - address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); - address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); - - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ - _proxy: payable(l1ERC721BridgeProxy), - _implementation: l1ERC721Bridge, - _data: abi.encodeCall( - IL1ERC721Bridge.initialize, - (ICrossDomainMessenger(payable(l1CrossDomainMessengerProxy)), ISuperchainConfig(superchainConfigProxy)) - ) - }); - - IL1ERC721Bridge bridge = IL1ERC721Bridge(l1ERC721BridgeProxy); - string memory version = bridge.version(); - console.log("L1ERC721Bridge version: %s", version); - - ChainAssertions.checkL1ERC721Bridge({ _contracts: _proxies(), _isProxy: true }); - } - - /// @notice Initialize the OptimismMintableERC20Factory - function initializeOptimismMintableERC20Factory() public broadcast { - console.log("Upgrading and initializing OptimismMintableERC20Factory proxy"); - address optimismMintableERC20FactoryProxy = mustGetAddress("OptimismMintableERC20FactoryProxy"); - address optimismMintableERC20Factory = mustGetAddress("OptimismMintableERC20Factory"); - address l1StandardBridgeProxy = mustGetAddress("L1StandardBridgeProxy"); - - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ - _proxy: payable(optimismMintableERC20FactoryProxy), - _implementation: optimismMintableERC20Factory, - _data: abi.encodeCall(IOptimismMintableERC20Factory.initialize, (l1StandardBridgeProxy)) - }); - - IOptimismMintableERC20Factory factory = IOptimismMintableERC20Factory(optimismMintableERC20FactoryProxy); - string memory version = factory.version(); - console.log("OptimismMintableERC20Factory version: %s", version); - - ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: _proxies(), _isProxy: true }); - } - - /// @notice initializeL1CrossDomainMessenger - function initializeL1CrossDomainMessenger() public broadcast { - console.log("Upgrading and initializing L1CrossDomainMessenger proxy"); - IProxyAdmin proxyAdmin = IProxyAdmin(mustGetAddress("ProxyAdmin")); - address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); - address l1CrossDomainMessenger = mustGetAddress("L1CrossDomainMessenger"); - address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); - address optimismPortalProxy = mustGetAddress("OptimismPortalProxy"); - address systemConfigProxy = mustGetAddress("SystemConfigProxy"); - - uint256 proxyType = uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)); - if (proxyType != uint256(IProxyAdmin.ProxyType.RESOLVED)) { - proxyAdmin.setProxyType(l1CrossDomainMessengerProxy, IProxyAdmin.ProxyType.RESOLVED); - } - require(uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)) == uint256(IProxyAdmin.ProxyType.RESOLVED)); - - string memory contractName = "OVM_L1CrossDomainMessenger"; - string memory implName = proxyAdmin.implementationName(l1CrossDomainMessenger); - if (keccak256(bytes(contractName)) != keccak256(bytes(implName))) { - proxyAdmin.setImplementationName(l1CrossDomainMessengerProxy, contractName); - } - require( - keccak256(bytes(proxyAdmin.implementationName(l1CrossDomainMessengerProxy))) - == keccak256(bytes(contractName)) - ); - - proxyAdmin.upgradeAndCall({ - _proxy: payable(l1CrossDomainMessengerProxy), - _implementation: l1CrossDomainMessenger, - _data: abi.encodeCall( - IL1CrossDomainMessenger.initialize, - ( - ISuperchainConfig(superchainConfigProxy), - IOptimismPortal(payable(optimismPortalProxy)), - ISystemConfig(systemConfigProxy) - ) - ) - }); - - IL1CrossDomainMessenger messenger = IL1CrossDomainMessenger(l1CrossDomainMessengerProxy); - string memory version = messenger.version(); - console.log("L1CrossDomainMessenger version: %s", version); - - ChainAssertions.checkL1CrossDomainMessenger({ _contracts: _proxies(), _vm: vm, _isProxy: true }); - } - /// @notice Initialize the L2OutputOracle function initializeL2OutputOracle() public broadcast { console.log("Upgrading and initializing L2OutputOracle proxy"); @@ -1185,59 +812,88 @@ contract Deploy is Deployer { /// @notice Initialize the OptimismPortal function initializeOptimismPortal() public broadcast { + console.log("Upgrading and initializing OptimismPortal proxy"); + require(!cfg.useFaultProofs(), "Deploy: FaultProofs OptimismPortal is initialized by OPCM"); address optimismPortalProxy = mustGetAddress("OptimismPortalProxy"); address systemConfigProxy = mustGetAddress("SystemConfigProxy"); address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); - if (cfg.useFaultProofs()) { - console.log("Upgrading and initializing OptimismPortal2 proxy"); - address optimismPortal2 = mustGetAddress("OptimismPortal2"); - address disputeGameFactoryProxy = mustGetAddress("DisputeGameFactoryProxy"); - - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ - _proxy: payable(optimismPortalProxy), - _implementation: optimismPortal2, - _data: abi.encodeCall( - IOptimismPortal2.initialize, - ( - IDisputeGameFactory(disputeGameFactoryProxy), - ISystemConfig(systemConfigProxy), - ISuperchainConfig(superchainConfigProxy), - GameType.wrap(uint32(cfg.respectedGameType())) - ) + address optimismPortal = mustGetAddress("OptimismPortal"); + address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy"); + + IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); + proxyAdmin.upgradeAndCall({ + _proxy: payable(optimismPortalProxy), + _implementation: optimismPortal, + _data: abi.encodeCall( + IOptimismPortal.initialize, + ( + IL2OutputOracle(l2OutputOracleProxy), + ISystemConfig(systemConfigProxy), + ISuperchainConfig(superchainConfigProxy) ) - }); + ) + }); - IOptimismPortal2 portal = IOptimismPortal2(payable(optimismPortalProxy)); - string memory version = portal.version(); - console.log("OptimismPortal2 version: %s", version); + IOptimismPortal portal = IOptimismPortal(payable(optimismPortalProxy)); + string memory version = portal.version(); + console.log("OptimismPortal version: %s", version); - ChainAssertions.checkOptimismPortal2({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); - } else { - console.log("Upgrading and initializing OptimismPortal proxy"); - address optimismPortal = mustGetAddress("OptimismPortal"); - address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy"); - - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ - _proxy: payable(optimismPortalProxy), - _implementation: optimismPortal, - _data: abi.encodeCall( - IOptimismPortal.initialize, - ( - IL2OutputOracle(l2OutputOracleProxy), - ISystemConfig(systemConfigProxy), - ISuperchainConfig(superchainConfigProxy) - ) - ) - }); + ChainAssertions.checkOptimismPortal({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); + } + + /// @notice Initialize the DataAvailabilityChallenge + function initializeDataAvailabilityChallenge() public broadcast { + console.log("Upgrading and initializing DataAvailabilityChallenge proxy"); + address dataAvailabilityChallengeProxy = mustGetAddress("DataAvailabilityChallengeProxy"); + address dataAvailabilityChallenge = mustGetAddress("DataAvailabilityChallenge"); + + address finalSystemOwner = cfg.finalSystemOwner(); + uint256 daChallengeWindow = cfg.daChallengeWindow(); + uint256 daResolveWindow = cfg.daResolveWindow(); + uint256 daBondSize = cfg.daBondSize(); + uint256 daResolverRefundPercentage = cfg.daResolverRefundPercentage(); + + IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); + proxyAdmin.upgradeAndCall({ + _proxy: payable(dataAvailabilityChallengeProxy), + _implementation: dataAvailabilityChallenge, + _data: abi.encodeCall( + IDataAvailabilityChallenge.initialize, + (finalSystemOwner, daChallengeWindow, daResolveWindow, daBondSize, daResolverRefundPercentage) + ) + }); + + IDataAvailabilityChallenge dac = IDataAvailabilityChallenge(payable(dataAvailabilityChallengeProxy)); + string memory version = dac.version(); + console.log("DataAvailabilityChallenge version: %s", version); + + require(dac.owner() == finalSystemOwner); + require(dac.challengeWindow() == daChallengeWindow); + require(dac.resolveWindow() == daResolveWindow); + require(dac.bondSize() == daBondSize); + require(dac.resolverRefundPercentage() == daResolverRefundPercentage); + } + + //////////////////////////////////////////////////////////////// + // Ownership Transfer Helper Functions // + //////////////////////////////////////////////////////////////// - IOptimismPortal portal = IOptimismPortal(payable(optimismPortalProxy)); - string memory version = portal.version(); - console.log("OptimismPortal version: %s", version); + /// @notice Transfer ownership of the ProxyAdmin contract to the final system owner + function transferProxyAdminOwnership() public broadcast { + // Get the ProxyAdmin contract. + IProxyAdmin proxyAdmin = IProxyAdmin(mustGetAddress("ProxyAdmin")); - ChainAssertions.checkOptimismPortal({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); + // Transfer ownership to the final system owner if necessary. + address owner = proxyAdmin.owner(); + address finalSystemOwner = cfg.finalSystemOwner(); + if (owner != finalSystemOwner) { + proxyAdmin.transferOwnership(finalSystemOwner); + console.log("ProxyAdmin ownership transferred to final system owner at: %s", finalSystemOwner); } + + // Make sure the ProxyAdmin owner is set to the final system owner. + owner = proxyAdmin.owner(); + require(owner == finalSystemOwner, "Deploy: ProxyAdmin ownership not transferred to final system owner"); } /// @notice Transfer ownership of the DisputeGameFactory contract to the final system owner @@ -1296,6 +952,10 @@ contract Deploy is Deployer { }); } + /////////////////////////////////////////////////////////// + // Proofs setup helper functions // + /////////////////////////////////////////////////////////// + /// @notice Load the appropriate mips absolute prestate for devenets depending on config environment. function loadMipsAbsolutePrestate() internal returns (Claim mipsAbsolutePrestate_) { if (block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet) { @@ -1317,17 +977,13 @@ contract Deploy is Deployer { function _loadDevnetStMipsAbsolutePrestate() internal returns (Claim mipsAbsolutePrestate_) { // Fetch the absolute prestate dump string memory filePath = string.concat(vm.projectRoot(), "/../../op-program/bin/prestate-proof.json"); - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat("[[ -f ", filePath, " ]] && echo \"present\""); - if (Process.run(commands).length == 0) { + if (bytes(Process.bash(string.concat("[[ -f ", filePath, " ]] && echo \"present\""))).length == 0) { revert( "Deploy: cannon prestate dump not found, generate it with `make cannon-prestate` in the monorepo root" ); } - commands[2] = string.concat("cat ", filePath, " | jq -r .pre"); - mipsAbsolutePrestate_ = Claim.wrap(abi.decode(Process.run(commands), (bytes32))); + mipsAbsolutePrestate_ = + Claim.wrap(abi.decode(bytes(Process.bash(string.concat("cat ", filePath, " | jq -r .pre"))), (bytes32))); console.log( "[Cannon Dispute Game] Using devnet MIPS Absolute prestate: %s", vm.toString(Claim.unwrap(mipsAbsolutePrestate_)) @@ -1339,25 +995,21 @@ contract Deploy is Deployer { function _loadDevnetMtMipsAbsolutePrestate() internal returns (Claim mipsAbsolutePrestate_) { // Fetch the absolute prestate dump string memory filePath = string.concat(vm.projectRoot(), "/../../op-program/bin/prestate-proof-mt.json"); - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat("[[ -f ", filePath, " ]] && echo \"present\""); - if (Process.run(commands).length == 0) { + if (bytes(Process.bash(string.concat("[[ -f ", filePath, " ]] && echo \"present\""))).length == 0) { revert( "Deploy: MT-Cannon prestate dump not found, generate it with `make cannon-prestate-mt` in the monorepo root" ); } - commands[2] = string.concat("cat ", filePath, " | jq -r .pre"); - mipsAbsolutePrestate_ = Claim.wrap(abi.decode(Process.run(commands), (bytes32))); + mipsAbsolutePrestate_ = + Claim.wrap(abi.decode(bytes(Process.bash(string.concat("cat ", filePath, " | jq -r .pre"))), (bytes32))); console.log( - "[MT-Cannon Dispute Game] Using devnet MIPS2 Absolute prestate: %s", + "[MT-Cannon Dispute Game] Using devnet MIPS64 Absolute prestate: %s", vm.toString(Claim.unwrap(mipsAbsolutePrestate_)) ); } /// @notice Sets the implementation for the `CANNON` game type in the `DisputeGameFactory` - function setCannonFaultGameImplementation(bool _allowUpgrade) public broadcast { + function setCannonFaultGameImplementation() public broadcast { console.log("Setting Cannon FaultDisputeGame implementation"); IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); IDelayedWETH weth = IDelayedWETH(mustGetAddress("DelayedWETHProxy")); @@ -1365,43 +1017,23 @@ contract Deploy is Deployer { // Set the Cannon FaultDisputeGame implementation in the factory. _setFaultGameImplementation({ _factory: factory, - _allowUpgrade: _allowUpgrade, - _params: FaultDisputeGameParams({ - anchorStateRegistry: IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), - weth: weth, + _params: IFaultDisputeGame.GameConstructorParams({ gameType: GameTypes.CANNON, absolutePrestate: loadMipsAbsolutePrestate(), - faultVm: IBigStepper(mustGetAddress("Mips")), maxGameDepth: cfg.faultGameMaxDepth(), - maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())) - }) - }); - } - - /// @notice Sets the implementation for the `PERMISSIONED_CANNON` game type in the `DisputeGameFactory` - function setPermissionedCannonFaultGameImplementation(bool _allowUpgrade) public broadcast { - console.log("Setting Cannon PermissionedDisputeGame implementation"); - IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); - IDelayedWETH weth = IDelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy")); - - // Set the Cannon FaultDisputeGame implementation in the factory. - _setFaultGameImplementation({ - _factory: factory, - _allowUpgrade: _allowUpgrade, - _params: FaultDisputeGameParams({ - anchorStateRegistry: IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), + splitDepth: cfg.faultGameSplitDepth(), + clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), + maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())), + vm: IBigStepper(mustGetAddress("Mips")), weth: weth, - gameType: GameTypes.PERMISSIONED_CANNON, - absolutePrestate: loadMipsAbsolutePrestate(), - faultVm: IBigStepper(mustGetAddress("Mips")), - maxGameDepth: cfg.faultGameMaxDepth(), - maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())) + anchorStateRegistry: IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), + l2ChainId: cfg.l2ChainID() }) }); } /// @notice Sets the implementation for the `ALPHABET` game type in the `DisputeGameFactory` - function setAlphabetFaultGameImplementation(bool _allowUpgrade) public onlyDevnet broadcast { + function setAlphabetFaultGameImplementation() public onlyDevnet broadcast { console.log("Setting Alphabet FaultDisputeGame implementation"); IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); IDelayedWETH weth = IDelayedWETH(mustGetAddress("DelayedWETHProxy")); @@ -1409,22 +1041,24 @@ contract Deploy is Deployer { Claim outputAbsolutePrestate = Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())); _setFaultGameImplementation({ _factory: factory, - _allowUpgrade: _allowUpgrade, - _params: FaultDisputeGameParams({ - anchorStateRegistry: IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), - weth: weth, + _params: IFaultDisputeGame.GameConstructorParams({ gameType: GameTypes.ALPHABET, absolutePrestate: outputAbsolutePrestate, - faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, IPreimageOracle(mustGetAddress("PreimageOracle")))), // The max depth for the alphabet trace is always 3. Add 1 because split depth is fully inclusive. maxGameDepth: cfg.faultGameSplitDepth() + 3 + 1, - maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())) + splitDepth: cfg.faultGameSplitDepth(), + clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), + maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())), + vm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, IPreimageOracle(mustGetAddress("PreimageOracle")))), + weth: weth, + anchorStateRegistry: IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), + l2ChainId: cfg.l2ChainID() }) }); } /// @notice Sets the implementation for the `ALPHABET` game type in the `DisputeGameFactory` - function setFastFaultGameImplementation(bool _allowUpgrade) public onlyDevnet broadcast { + function setFastFaultGameImplementation() public onlyDevnet broadcast { console.log("Setting Fast FaultDisputeGame implementation"); IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); IDelayedWETH weth = IDelayedWETH(mustGetAddress("DelayedWETHProxy")); @@ -1443,28 +1077,32 @@ contract Deploy is Deployer { ); _setFaultGameImplementation({ _factory: factory, - _allowUpgrade: _allowUpgrade, - _params: FaultDisputeGameParams({ - anchorStateRegistry: IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), - weth: weth, + _params: IFaultDisputeGame.GameConstructorParams({ gameType: GameTypes.FAST, absolutePrestate: outputAbsolutePrestate, - faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, fastOracle)), // The max depth for the alphabet trace is always 3. Add 1 because split depth is fully inclusive. maxGameDepth: cfg.faultGameSplitDepth() + 3 + 1, - maxClockDuration: Duration.wrap(0) // Resolvable immediately - }) + splitDepth: cfg.faultGameSplitDepth(), + clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), + maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())), // fastGame is not enabled in + // FP-devnet, set with faultGameMaxClockDuration to avoid compilation error + vm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, fastOracle)), + weth: weth, + anchorStateRegistry: IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), + l2ChainId: cfg.l2ChainID() + }) }); } /// @notice Sets the implementation for the given fault game type in the `DisputeGameFactory`. function _setFaultGameImplementation( IDisputeGameFactory _factory, - bool _allowUpgrade, - FaultDisputeGameParams memory _params + IFaultDisputeGame.GameConstructorParams memory _params ) internal { + // we alloc upgrade to be called here only in devnet + bool _allowUpgrade = true; if (address(_factory.gameImpls(_params.gameType)) != address(0) && !_allowUpgrade) { console.log( "[WARN] DisputeGameFactoryProxy: `FaultDisputeGame` implementation already set for game type: %s", @@ -1474,75 +1112,26 @@ contract Deploy is Deployer { } uint32 rawGameType = GameType.unwrap(_params.gameType); + require( + rawGameType != GameTypes.PERMISSIONED_CANNON.raw(), "Deploy: Permissioned Game should be deployed by OPCM" + ); - // Redefine _param variable to avoid stack too deep error during compilation - FaultDisputeGameParams memory _params_ = _params; - if (rawGameType != GameTypes.PERMISSIONED_CANNON.raw()) { - _factory.setImplementation( - _params_.gameType, - IDisputeGame( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "FaultDisputeGame", - _nick: string.concat("FaultDisputeGame_", vm.toString(rawGameType)), - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGame.__constructor__, - ( - _params_.gameType, - _params_.absolutePrestate, - _params_.maxGameDepth, - cfg.faultGameSplitDepth(), - Duration.wrap(uint64(cfg.faultGameClockExtension())), - _params_.maxClockDuration, - _params_.faultVm, - _params_.weth, - IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), - cfg.l2ChainID() - ) - ) - ) - }) - ) - ); - } else { - _factory.setImplementation( - _params_.gameType, - IDisputeGame( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "PermissionedDisputeGame", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IPermissionedDisputeGame.__constructor__, - ( - _params_.gameType, - _params_.absolutePrestate, - _params_.maxGameDepth, - cfg.faultGameSplitDepth(), - Duration.wrap(uint64(cfg.faultGameClockExtension())), - _params_.maxClockDuration, - _params_.faultVm, - _params_.weth, - _params_.anchorStateRegistry, - cfg.l2ChainID(), - cfg.l2OutputOracleProposer(), - cfg.l2OutputOracleChallenger() - ) - ) - ) - }) - ) - ); - } + _factory.setImplementation( + _params.gameType, + IDisputeGame( + DeployUtils.create2AndSave({ + _save: this, + _salt: _implSalt(), + _name: "FaultDisputeGame", + _nick: string.concat("FaultDisputeGame_", vm.toString(rawGameType)), + _args: DeployUtils.encodeConstructor(abi.encodeCall(IFaultDisputeGame.__constructor__, (_params))) + }) + ) + ); string memory gameTypeString; if (rawGameType == GameTypes.CANNON.raw()) { gameTypeString = "Cannon"; - } else if (rawGameType == GameTypes.PERMISSIONED_CANNON.raw()) { - gameTypeString = "PermissionedCannon"; } else if (rawGameType == GameTypes.ALPHABET.raw()) { gameTypeString = "Alphabet"; } else { @@ -1556,36 +1145,60 @@ contract Deploy is Deployer { ); } - /// @notice Initialize the DataAvailabilityChallenge - function initializeDataAvailabilityChallenge() public broadcast { - console.log("Upgrading and initializing DataAvailabilityChallenge proxy"); - address dataAvailabilityChallengeProxy = mustGetAddress("DataAvailabilityChallengeProxy"); - address dataAvailabilityChallenge = mustGetAddress("DataAvailabilityChallenge"); - - address finalSystemOwner = cfg.finalSystemOwner(); - uint256 daChallengeWindow = cfg.daChallengeWindow(); - uint256 daResolveWindow = cfg.daResolveWindow(); - uint256 daBondSize = cfg.daBondSize(); - uint256 daResolverRefundPercentage = cfg.daResolverRefundPercentage(); - - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ - _proxy: payable(dataAvailabilityChallengeProxy), - _implementation: dataAvailabilityChallenge, - _data: abi.encodeCall( - IDataAvailabilityChallenge.initialize, - (finalSystemOwner, daChallengeWindow, daResolveWindow, daBondSize, daResolverRefundPercentage) - ) + /// @notice Get the DeployInput struct to use for testing + function getDeployInput() public view returns (OPContractsManager.DeployInput memory) { + OutputRoot memory testOutputRoot = OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() }); + IAnchorStateRegistry.StartingAnchorRoot[] memory startingAnchorRoots = + new IAnchorStateRegistry.StartingAnchorRoot[](5); + startingAnchorRoots[0] = + IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.CANNON, outputRoot: testOutputRoot }); + startingAnchorRoots[1] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.PERMISSIONED_CANNON, + outputRoot: testOutputRoot + }); + startingAnchorRoots[2] = + IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.ASTERISC, outputRoot: testOutputRoot }); + startingAnchorRoots[3] = + IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.FAST, outputRoot: testOutputRoot }); + startingAnchorRoots[4] = + IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.ALPHABET, outputRoot: testOutputRoot }); + string memory saltMixer = "salt mixer"; + return OPContractsManager.DeployInput({ + roles: OPContractsManager.Roles({ + opChainProxyAdminOwner: msg.sender, + systemConfigOwner: cfg.finalSystemOwner(), + batcher: cfg.batchSenderAddress(), + unsafeBlockSigner: cfg.p2pSequencerAddress(), + proposer: cfg.l2OutputOracleProposer(), + challenger: cfg.l2OutputOracleChallenger() + }), + basefeeScalar: cfg.basefeeScalar(), + blobBasefeeScalar: cfg.blobbasefeeScalar(), + l2ChainId: cfg.l2ChainID(), + startingAnchorRoots: abi.encode(startingAnchorRoots), + saltMixer: saltMixer, + gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), + disputeGameType: GameTypes.PERMISSIONED_CANNON, + disputeAbsolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), + disputeMaxGameDepth: cfg.faultGameMaxDepth(), + disputeSplitDepth: cfg.faultGameSplitDepth(), + disputeClockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), + disputeMaxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())) + }); + } - IDataAvailabilityChallenge dac = IDataAvailabilityChallenge(payable(dataAvailabilityChallengeProxy)); - string memory version = dac.version(); - console.log("DataAvailabilityChallenge version: %s", version); - - require(dac.owner() == finalSystemOwner); - require(dac.challengeWindow() == daChallengeWindow); - require(dac.resolveWindow() == daResolveWindow); - require(dac.bondSize() == daBondSize); - require(dac.resolverRefundPercentage() == daResolverRefundPercentage); + /// @notice Reset the initialized value on a proxy contract so that it can be initialized again + function resetInitializedProxy(string memory _contractName) internal { + console.log("resetting initialized value on %s Proxy", _contractName); + address proxy = mustGetAddress(string.concat(_contractName, "Proxy")); + StorageSlot memory slot = ForgeArtifacts.getInitializedSlot(_contractName); + bytes32 slotVal = vm.load(proxy, bytes32(vm.parseUint(slot.slot))); + uint256 value = uint256(slotVal); + value = value & ~(0xFF << (slot.offset * 8)); + slotVal = bytes32(value); + vm.store(proxy, bytes32(vm.parseUint(slot.slot)), slotVal); } } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployAltDA.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployAltDA.s.sol new file mode 100644 index 0000000000000..1943b40a35bb5 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployAltDA.s.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { Script } from "forge-std/Script.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { Solarray } from "scripts/libraries/Solarray.sol"; + +contract DeployAltDAInput is BaseDeployIO { + bytes32 internal _salt; + IProxyAdmin internal _proxyAdmin; + address internal _challengeContractOwner; + uint256 internal _challengeWindow; + uint256 internal _resolveWindow; + uint256 internal _bondSize; + uint256 internal _resolverRefundPercentage; + + function set(bytes4 _sel, bytes32 _val) public { + if (_sel == this.salt.selector) _salt = _val; + else revert("DeployAltDAInput: unknown selector"); + } + + function set(bytes4 _sel, address _addr) public { + require(_addr != address(0), "DeployAltDAInput: cannot set zero address"); + if (_sel == this.proxyAdmin.selector) _proxyAdmin = IProxyAdmin(_addr); + else if (_sel == this.challengeContractOwner.selector) _challengeContractOwner = _addr; + else revert("DeployAltDAInput: unknown selector"); + } + + function set(bytes4 _sel, uint256 _val) public { + if (_sel == this.challengeWindow.selector) _challengeWindow = _val; + else if (_sel == this.resolveWindow.selector) _resolveWindow = _val; + else if (_sel == this.bondSize.selector) _bondSize = _val; + else if (_sel == this.resolverRefundPercentage.selector) _resolverRefundPercentage = _val; + else revert("DeployAltDAInput: unknown selector"); + } + + function salt() public view returns (bytes32) { + require(_salt != 0, "DeployAltDAInput: salt not set"); + return _salt; + } + + function proxyAdmin() public view returns (IProxyAdmin) { + require(address(_proxyAdmin) != address(0), "DeployAltDAInput: proxyAdmin not set"); + return _proxyAdmin; + } + + function challengeContractOwner() public view returns (address) { + require(_challengeContractOwner != address(0), "DeployAltDAInput: challengeContractOwner not set"); + return _challengeContractOwner; + } + + function challengeWindow() public view returns (uint256) { + require(_challengeWindow != 0, "DeployAltDAInput: challengeWindow not set"); + return _challengeWindow; + } + + function resolveWindow() public view returns (uint256) { + require(_resolveWindow != 0, "DeployAltDAInput: resolveWindow not set"); + return _resolveWindow; + } + + function bondSize() public view returns (uint256) { + require(_bondSize != 0, "DeployAltDAInput: bondSize not set"); + return _bondSize; + } + + function resolverRefundPercentage() public view returns (uint256) { + require(_resolverRefundPercentage != 0, "DeployAltDAInput: resolverRefundPercentage not set"); + return _resolverRefundPercentage; + } +} + +contract DeployAltDAOutput is BaseDeployIO { + IDataAvailabilityChallenge internal _dataAvailabilityChallengeProxy; + IDataAvailabilityChallenge internal _dataAvailabilityChallengeImpl; + + function set(bytes4 _sel, address _addr) public { + require(_addr != address(0), "DeployAltDAOutput: cannot set zero address"); + if (_sel == this.dataAvailabilityChallengeProxy.selector) { + _dataAvailabilityChallengeProxy = IDataAvailabilityChallenge(payable(_addr)); + } else if (_sel == this.dataAvailabilityChallengeImpl.selector) { + _dataAvailabilityChallengeImpl = IDataAvailabilityChallenge(payable(_addr)); + } else { + revert("DeployAltDAOutput: unknown selector"); + } + } + + function dataAvailabilityChallengeProxy() public view returns (IDataAvailabilityChallenge) { + DeployUtils.assertValidContractAddress(address(_dataAvailabilityChallengeProxy)); + return _dataAvailabilityChallengeProxy; + } + + function dataAvailabilityChallengeImpl() public view returns (IDataAvailabilityChallenge) { + DeployUtils.assertValidContractAddress(address(_dataAvailabilityChallengeImpl)); + return _dataAvailabilityChallengeImpl; + } +} + +contract DeployAltDA is Script { + function run(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + deployDataAvailabilityChallengeProxy(_dai, _dao); + deployDataAvailabilityChallengeImpl(_dai, _dao); + initializeDataAvailabilityChallengeProxy(_dai, _dao); + + checkOutput(_dai, _dao); + } + + function deployDataAvailabilityChallengeProxy(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + bytes32 salt = _dai.salt(); + vm.broadcast(msg.sender); + IProxy proxy = IProxy( + DeployUtils.create2({ + _name: "Proxy", + _salt: salt, + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender))) + }) + ); + vm.label(address(proxy), "DataAvailabilityChallengeProxy"); + _dao.set(_dao.dataAvailabilityChallengeProxy.selector, address(proxy)); + } + + function deployDataAvailabilityChallengeImpl(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + bytes32 salt = _dai.salt(); + vm.broadcast(msg.sender); + IDataAvailabilityChallenge impl = IDataAvailabilityChallenge( + DeployUtils.create2({ + _name: "DataAvailabilityChallenge", + _salt: salt, + _args: DeployUtils.encodeConstructor(abi.encodeCall(IDataAvailabilityChallenge.__constructor__, ())) + }) + ); + vm.label(address(impl), "DataAvailabilityChallengeImpl"); + _dao.set(_dao.dataAvailabilityChallengeImpl.selector, address(impl)); + } + + function initializeDataAvailabilityChallengeProxy(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + IProxy proxy = IProxy(payable(address(_dao.dataAvailabilityChallengeProxy()))); + IDataAvailabilityChallenge impl = _dao.dataAvailabilityChallengeImpl(); + IProxyAdmin proxyAdmin = IProxyAdmin(payable(address(_dai.proxyAdmin()))); + + address contractOwner = _dai.challengeContractOwner(); + uint256 challengeWindow = _dai.challengeWindow(); + uint256 resolveWindow = _dai.resolveWindow(); + uint256 bondSize = _dai.bondSize(); + uint256 resolverRefundPercentage = _dai.resolverRefundPercentage(); + + vm.startBroadcast(msg.sender); + proxy.upgradeToAndCall( + address(impl), + abi.encodeCall( + IDataAvailabilityChallenge.initialize, + (contractOwner, challengeWindow, resolveWindow, bondSize, resolverRefundPercentage) + ) + ); + proxy.changeAdmin(address(proxyAdmin)); + vm.stopBroadcast(); + } + + function checkOutput(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + address[] memory addresses = Solarray.addresses( + address(_dao.dataAvailabilityChallengeProxy()), address(_dao.dataAvailabilityChallengeImpl()) + ); + DeployUtils.assertValidContractAddresses(addresses); + + assertValidDataAvailabilityChallengeProxy(_dai, _dao); + assertValidDataAvailabilityChallengeImpl(_dao); + } + + function assertValidDataAvailabilityChallengeProxy(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + DeployUtils.assertERC1967ImplementationSet(address(_dao.dataAvailabilityChallengeProxy())); + + IProxy proxy = IProxy(payable(address(_dao.dataAvailabilityChallengeProxy()))); + vm.prank(address(0)); + address admin = proxy.admin(); + require(admin == address(_dai.proxyAdmin()), "DACP-10"); + + DeployUtils.assertInitialized({ _contractAddress: address(proxy), _slot: 0, _offset: 0 }); + + vm.prank(address(0)); + address impl = proxy.implementation(); + require(impl == address(_dao.dataAvailabilityChallengeImpl()), "DACP-20"); + + IDataAvailabilityChallenge dac = _dao.dataAvailabilityChallengeProxy(); + require(dac.owner() == _dai.challengeContractOwner(), "DACP-30"); + require(dac.challengeWindow() == _dai.challengeWindow(), "DACP-40"); + require(dac.resolveWindow() == _dai.resolveWindow(), "DACP-50"); + require(dac.bondSize() == _dai.bondSize(), "DACP-60"); + require(dac.resolverRefundPercentage() == _dai.resolverRefundPercentage(), "DACP-70"); + } + + function assertValidDataAvailabilityChallengeImpl(DeployAltDAOutput _dao) public view { + IDataAvailabilityChallenge dac = _dao.dataAvailabilityChallengeImpl(); + DeployUtils.assertInitialized({ _contractAddress: address(dac), _slot: 0, _offset: 0 }); + } +} diff --git a/packages/contracts-bedrock/scripts/deploy/DeployAsterisc.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployAsterisc.s.sol new file mode 100644 index 0000000000000..66f6ba33f362f --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployAsterisc.s.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Forge +import { Script } from "forge-std/Script.sol"; + +// Scripts +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Interfaces +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { IRISCV } from "interfaces/vendor/asterisc/IRISCV.sol"; + +/// @title DeployAsteriscInput +contract DeployAsteriscInput is BaseDeployIO { + // Specify the PreimageOracle to use + address internal _preimageOracle; + + function set(bytes4 _sel, address _value) public { + if (_sel == this.preimageOracle.selector) { + require(_value != address(0), "DeployAsterisc: preimageOracle cannot be empty"); + _preimageOracle = _value; + } else { + revert("DeployAsterisc: unknown selector"); + } + } + + function preimageOracle() public view returns (address) { + require(_preimageOracle != address(0), "DeployAsterisc: preimageOracle not set"); + return _preimageOracle; + } +} + +/// @title DeployAsteriscOutput +contract DeployAsteriscOutput is BaseDeployIO { + IRISCV internal _asteriscSingleton; + + function set(bytes4 _sel, address _value) public { + if (_sel == this.asteriscSingleton.selector) { + require(_value != address(0), "DeployAsterisc: asteriscSingleton cannot be zero address"); + _asteriscSingleton = IRISCV(_value); + } else { + revert("DeployAsterisc: unknown selector"); + } + } + + function checkOutput(DeployAsteriscInput _mi) public view { + DeployUtils.assertValidContractAddress(address(_asteriscSingleton)); + assertValidDeploy(_mi); + } + + function asteriscSingleton() public view returns (IRISCV) { + DeployUtils.assertValidContractAddress(address(_asteriscSingleton)); + return _asteriscSingleton; + } + + function assertValidDeploy(DeployAsteriscInput _mi) public view { + assertValidAsteriscSingleton(_mi); + } + + function assertValidAsteriscSingleton(DeployAsteriscInput _mi) internal view { + IRISCV asterisc = asteriscSingleton(); + + require(address(asterisc.oracle()) == address(_mi.preimageOracle()), "ASTERISC-10"); + } +} + +/// @title DeployAsterisc +contract DeployAsterisc is Script { + function run(DeployAsteriscInput _mi, DeployAsteriscOutput _mo) public { + DeployAsteriscSingleton(_mi, _mo); + _mo.checkOutput(_mi); + } + + function DeployAsteriscSingleton(DeployAsteriscInput _mi, DeployAsteriscOutput _mo) internal { + IPreimageOracle preimageOracle = IPreimageOracle(_mi.preimageOracle()); + vm.broadcast(msg.sender); + IRISCV singleton = IRISCV( + DeployUtils.create1({ + _name: "RISCV", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IRISCV.__constructor__, (preimageOracle))) + }) + ); + + vm.label(address(singleton), "AsteriscSingleton"); + _mo.set(_mo.asteriscSingleton.selector, address(singleton)); + } +} diff --git a/packages/contracts-bedrock/scripts/DeployAuthSystem.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployAuthSystem.s.sol similarity index 100% rename from packages/contracts-bedrock/scripts/DeployAuthSystem.s.sol rename to packages/contracts-bedrock/scripts/deploy/DeployAuthSystem.s.sol diff --git a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol index 9288111b6e6bb..38f3f1d1cdcda 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.15; import { Script } from "forge-std/Script.sol"; import { console2 as console } from "forge-std/console2.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { Executables } from "scripts/libraries/Executables.sol"; import { Process } from "scripts/libraries/Process.sol"; import { Config, Fork, ForkUtils } from "scripts/libraries/Config.sol"; @@ -30,6 +29,7 @@ contract DeployConfig is Script { uint256 public l2GenesisEcotoneTimeOffset; uint256 public l2GenesisFjordTimeOffset; uint256 public l2GenesisGraniteTimeOffset; + uint256 public l2GenesisHoloceneTimeOffset; uint256 public maxSequencerDrift; uint256 public sequencerWindowSize; uint256 public channelTimeout; @@ -90,6 +90,8 @@ contract DeployConfig is Script { address public customGasTokenAddress; bool public useInterop; + bool public useSoulGasToken; + bool public isSoulBackedByNative; function read(string memory _path) public { console.log("DeployConfig: reading file %s", _path); @@ -109,6 +111,7 @@ contract DeployConfig is Script { l2GenesisEcotoneTimeOffset = _readOr(_json, "$.l2GenesisEcotoneTimeOffset", NULL_OFFSET); l2GenesisFjordTimeOffset = _readOr(_json, "$.l2GenesisFjordTimeOffset", NULL_OFFSET); l2GenesisGraniteTimeOffset = _readOr(_json, "$.l2GenesisGraniteTimeOffset", NULL_OFFSET); + l2GenesisHoloceneTimeOffset = _readOr(_json, "$.l2GenesisHoloceneTimeOffset", NULL_OFFSET); maxSequencerDrift = stdJson.readUint(_json, "$.maxSequencerDrift"); sequencerWindowSize = stdJson.readUint(_json, "$.sequencerWindowSize"); @@ -175,6 +178,8 @@ contract DeployConfig is Script { customGasTokenAddress = _readOr(_json, "$.customGasTokenAddress", address(0)); useInterop = _readOr(_json, "$.useInterop", false); + useSoulGasToken = _readOr(_json, "$.useSoulGasToken", false); + isSoulBackedByNative = _readOr(_json, "$.isSoulBackedByNative", false); } function fork() public view returns (Fork fork_) { @@ -209,12 +214,9 @@ contract DeployConfig is Script { function l2OutputOracleStartingTimestamp() public returns (uint256) { if (_l2OutputOracleStartingTimestamp < 0) { bytes32 tag = l1StartingBlockTag(); - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat("cast block ", vm.toString(tag), " --json | ", Executables.jq, " .timestamp"); - bytes memory res = Process.run(cmd); - return stdJson.readUint(string(res), ""); + string memory cmd = string.concat("cast block ", vm.toString(tag), " --json | jq .timestamp"); + string memory res = Process.bash(cmd); + return stdJson.readUint(res, ""); } return uint256(_l2OutputOracleStartingTimestamp); } @@ -234,6 +236,16 @@ contract DeployConfig is Script { useInterop = _useInterop; } + /// @notice Allow the `useSoulGasToken` config to be overridden in testing environments + function setUseSoulGasToken(bool _useSoulGasToken) public { + useSoulGasToken = _useSoulGasToken; + } + + /// @notice Allow the `isSoulBackedByNative` config to be overridden in testing environments + function setIsSoulBackedByNative(bool _isSoulBackedByNative) public { + isSoulBackedByNative = _isSoulBackedByNative; + } + /// @notice Allow the `fundDevAccounts` config to be overridden. function setFundDevAccounts(bool _fundDevAccounts) public { fundDevAccounts = _fundDevAccounts; @@ -246,7 +258,9 @@ contract DeployConfig is Script { } function latestGenesisFork() internal view returns (Fork) { - if (l2GenesisGraniteTimeOffset == 0) { + if (l2GenesisHoloceneTimeOffset == 0) { + return Fork.HOLOCENE; + } else if (l2GenesisGraniteTimeOffset == 0) { return Fork.GRANITE; } else if (l2GenesisFjordTimeOffset == 0) { return Fork.FJORD; @@ -259,16 +273,13 @@ contract DeployConfig is Script { } function _getBlockByTag(string memory _tag) internal returns (bytes32) { - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat("cast block ", _tag, " --json | ", Executables.jq, " -r .hash"); - bytes memory res = Process.run(cmd); + string memory cmd = string.concat("cast block ", _tag, " --json | jq -r .hash"); + bytes memory res = bytes(Process.bash(cmd)); return abi.decode(res, (bytes32)); } function _readOr(string memory _jsonInp, string memory _key, bool _defaultValue) internal view returns (bool) { - return vm.keyExistsJson(_jsonInp, _key) ? _jsonInp.readBool(_key) : _defaultValue; + return _jsonInp.readBoolOr(_key, _defaultValue); } function _readOr( @@ -292,7 +303,7 @@ contract DeployConfig is Script { view returns (address) { - return vm.keyExistsJson(_jsonInp, _key) ? _jsonInp.readAddress(_key) : _defaultValue; + return _jsonInp.readAddressOr(_key, _defaultValue); } function _isNull(string memory _jsonInp, string memory _key) internal pure returns (bool) { @@ -309,6 +320,6 @@ contract DeployConfig is Script { view returns (string memory) { - return vm.keyExists(_jsonInp, _key) ? _jsonInp.readString(_key) : _defaultValue; + return _jsonInp.readStringOr(_key, _defaultValue); } } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol new file mode 100644 index 0000000000000..eb386f2ff4061 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Forge +import { Script } from "forge-std/Script.sol"; + +// Scripts +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Libraries +import { LibString } from "@solady/utils/LibString.sol"; + +// Interfaces +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +/// @title DeployDelayedWETH +contract DeployDelayedWETHInput is BaseDeployIO { + /// Required inputs. + string internal _release; + address public _proxyAdmin; + ISuperchainConfig public _superchainConfigProxy; + address public _delayedWethImpl; + address public _delayedWethOwner; + uint256 public _delayedWethDelay; + + function set(bytes4 _sel, uint256 _value) public { + if (_sel == this.delayedWethDelay.selector) { + require(_value != 0, "DeployDelayedWETH: delayedWethDelay cannot be zero"); + _delayedWethDelay = _value; + } else { + revert("DeployDelayedWETH: unknown selector"); + } + } + + function set(bytes4 _sel, address _value) public { + if (_sel == this.proxyAdmin.selector) { + require(_value != address(0), "DeployDelayedWETH: proxyAdmin cannot be zero address"); + _proxyAdmin = _value; + } else if (_sel == this.superchainConfigProxy.selector) { + require(_value != address(0), "DeployDelayedWETH: superchainConfigProxy cannot be zero address"); + _superchainConfigProxy = ISuperchainConfig(_value); + } else if (_sel == this.delayedWethOwner.selector) { + require(_value != address(0), "DeployDelayedWETH: delayedWethOwner cannot be zero address"); + _delayedWethOwner = _value; + } else if (_sel == this.delayedWethImpl.selector) { + _delayedWethImpl = _value; + } else { + revert("DeployDelayedWETH: unknown selector"); + } + } + + function set(bytes4 _sel, string memory _value) public { + if (_sel == this.release.selector) { + require(!LibString.eq(_value, ""), "DeployDelayedWETH: release cannot be empty"); + _release = _value; + } else { + revert("DeployDelayedWETH: unknown selector"); + } + } + + function release() public view returns (string memory) { + require(!LibString.eq(_release, ""), "DeployDelayedWETH: release not set"); + return _release; + } + + function proxyAdmin() public view returns (address) { + require(_proxyAdmin != address(0), "DeployDelayedWETH: proxyAdmin not set"); + return _proxyAdmin; + } + + function superchainConfigProxy() public view returns (ISuperchainConfig) { + require(address(_superchainConfigProxy) != address(0), "DeployDisputeGame: superchainConfigProxy not set"); + return _superchainConfigProxy; + } + + function delayedWethImpl() public view returns (address) { + require(_delayedWethImpl != address(0), "DeployDelayedWETH: delayedWethImpl not set"); + return _delayedWethImpl; + } + + function delayedWethOwner() public view returns (address) { + require(_delayedWethOwner != address(0), "DeployDelayedWETH: delayedWethOwner not set"); + return _delayedWethOwner; + } + + function delayedWethDelay() public view returns (uint256) { + require(_delayedWethDelay != 0, "DeployDelayedWETH: delayedWethDelay not set"); + return _delayedWethDelay; + } +} + +/// @title DeployDelayedWETHOutput +contract DeployDelayedWETHOutput is BaseDeployIO { + IDelayedWETH internal _delayedWethImpl; + IDelayedWETH internal _delayedWethProxy; + + function set(bytes4 _sel, address _value) public { + if (_sel == this.delayedWethImpl.selector) { + require(_value != address(0), "DeployDelayedWETHOutput: delayedWethImpl cannot be zero address"); + _delayedWethImpl = IDelayedWETH(payable(_value)); + } else if (_sel == this.delayedWethProxy.selector) { + require(_value != address(0), "DeployDelayedWETHOutput: delayedWethProxy cannot be zero address"); + _delayedWethProxy = IDelayedWETH(payable(_value)); + } else { + revert("DeployDelayedWETHOutput: unknown selector"); + } + } + + function checkOutput(DeployDelayedWETHInput _dwi) public { + DeployUtils.assertValidContractAddress(address(_delayedWethImpl)); + DeployUtils.assertValidContractAddress(address(_delayedWethProxy)); + assertValidDeploy(_dwi); + } + + function delayedWethImpl() public view returns (IDelayedWETH) { + DeployUtils.assertValidContractAddress(address(_delayedWethImpl)); + return _delayedWethImpl; + } + + function delayedWethProxy() public view returns (IDelayedWETH) { + DeployUtils.assertValidContractAddress(address(_delayedWethProxy)); + return _delayedWethProxy; + } + + function assertValidDeploy(DeployDelayedWETHInput _dwi) public { + assertValidDelayedWethImpl(_dwi); + assertValidDelayedWethProxy(_dwi); + } + + function assertValidDelayedWethImpl(DeployDelayedWETHInput _dwi) internal { + IProxy proxy = IProxy(payable(address(delayedWethProxy()))); + vm.prank(address(0)); + address impl = proxy.implementation(); + require(impl == address(delayedWethImpl()), "DWI-10"); + DeployUtils.assertInitialized({ _contractAddress: address(delayedWethImpl()), _slot: 0, _offset: 0 }); + require(delayedWethImpl().owner() == address(0), "DWI-20"); + require(delayedWethImpl().delay() == _dwi.delayedWethDelay(), "DWI-30"); + require(address(delayedWethImpl().config()) == address(0), "DWI-30"); + } + + function assertValidDelayedWethProxy(DeployDelayedWETHInput _dwi) internal { + // Check as proxy. + IProxy proxy = IProxy(payable(address(delayedWethProxy()))); + vm.prank(address(0)); + address admin = proxy.admin(); + require(admin == _dwi.proxyAdmin(), "DWP-10"); + + // Check as implementation. + DeployUtils.assertInitialized({ _contractAddress: address(delayedWethProxy()), _slot: 0, _offset: 0 }); + require(delayedWethProxy().owner() == _dwi.delayedWethOwner(), "DWP-20"); + require(delayedWethProxy().delay() == _dwi.delayedWethDelay(), "DWP-30"); + require(delayedWethProxy().config() == _dwi.superchainConfigProxy(), "DWP-40"); + } +} + +/// @title DeployDelayedWETH +contract DeployDelayedWETH is Script { + function run(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) public { + deployDelayedWethProxy(_dwi, _dwo); + _dwo.checkOutput(_dwi); + } + + function deployDelayedWethImpl(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) internal { + string memory release = _dwi.release(); + IDelayedWETH impl; + + address existingImplementation = _dwi.delayedWethImpl(); + if (existingImplementation != address(0)) { + impl = IDelayedWETH(payable(existingImplementation)); + } else if (isDevelopRelease(release)) { + vm.broadcast(msg.sender); + impl = IDelayedWETH( + DeployUtils.create1({ + _name: "DelayedWETH", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IDelayedWETH.__constructor__, (_dwi.delayedWethDelay())) + ) + }) + ); + } else { + revert(string.concat("DeployDelayedWETH: failed to deploy release ", release)); + } + + vm.label(address(impl), "DelayedWETHImpl"); + _dwo.set(_dwo.delayedWethImpl.selector, address(impl)); + } + + function deployDelayedWethProxy(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) internal { + vm.broadcast(msg.sender); + IProxy proxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender))) + }) + ); + + deployDelayedWethImpl(_dwi, _dwo); + IDelayedWETH impl = _dwo.delayedWethImpl(); + + vm.startBroadcast(msg.sender); + proxy.upgradeToAndCall( + address(impl), abi.encodeCall(impl.initialize, (_dwi.delayedWethOwner(), _dwi.superchainConfigProxy())) + ); + proxy.changeAdmin(_dwi.proxyAdmin()); + vm.stopBroadcast(); + + vm.label(address(proxy), "DelayedWETHProxy"); + _dwo.set(_dwo.delayedWethProxy.selector, address(proxy)); + } + + // A release is considered a 'develop' release if it does not start with 'op-contracts'. + function isDevelopRelease(string memory _release) internal pure returns (bool) { + return !LibString.startsWith(_release, "op-contracts"); + } +} diff --git a/packages/contracts-bedrock/scripts/deploy/DeployDisputeGame.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployDisputeGame.s.sol new file mode 100644 index 0000000000000..6399115fad745 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployDisputeGame.s.sol @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Forge +import { Script } from "forge-std/Script.sol"; + +// Scripts +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Libraries +import { GameType, Claim, Duration } from "src/dispute/lib/Types.sol"; +import { LibString } from "@solady/utils/LibString.sol"; + +// Interfaces +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; + +/// @title DeployDisputeGameInput +contract DeployDisputeGameInput is BaseDeployIO { + // Common inputs. + string internal _release; + string internal _standardVersionsToml; + + // Specify which game kind is being deployed here. + string internal _gameKind; + + // All inputs required to deploy FaultDisputeGame. + uint256 internal _gameType; + bytes32 internal _absolutePrestate; + uint256 internal _maxGameDepth; + uint256 internal _splitDepth; + uint256 internal _clockExtension; + uint256 internal _maxClockDuration; + IDelayedWETH internal _delayedWethProxy; + IAnchorStateRegistry internal _anchorStateRegistryProxy; + IBigStepper internal _vm; + uint256 internal _l2ChainId; + + // Additional inputs required to deploy PermissionedDisputeGame. + address internal _proposer; + address internal _challenger; + + function set(bytes4 _sel, uint256 _value) public { + if (_sel == this.gameType.selector) { + require(_value <= type(uint32).max, "DeployDisputeGame: gameType must fit inside uint32"); + _gameType = _value; + } else if (_sel == this.maxGameDepth.selector) { + require(_value != 0, "DeployDisputeGame: maxGameDepth cannot be zero"); + _maxGameDepth = _value; + } else if (_sel == this.splitDepth.selector) { + require(_value != 0, "DeployDisputeGame: splitDepth cannot be zero"); + _splitDepth = _value; + } else if (_sel == this.clockExtension.selector) { + require(_value <= type(uint64).max, "DeployDisputeGame: clockExtension must fit inside uint64"); + require(_value != 0, "DeployDisputeGame: clockExtension cannot be zero"); + _clockExtension = _value; + } else if (_sel == this.maxClockDuration.selector) { + require(_value <= type(uint64).max, "DeployDisputeGame: maxClockDuration must fit inside uint64"); + require(_value != 0, "DeployDisputeGame: maxClockDuration cannot be zero"); + _maxClockDuration = _value; + } else if (_sel == this.l2ChainId.selector) { + require(_value != 0, "DeployDisputeGame: l2ChainId cannot be zero"); + _l2ChainId = _value; + } else { + revert("DeployDisputeGame: unknown selector"); + } + } + + function set(bytes4 _sel, address _value) public { + if (_sel == this.vmAddress.selector) { + _vm = IBigStepper(_value); + } else if (_sel == this.delayedWethProxy.selector) { + require(_value != address(0), "DeployDisputeGame: delayedWethProxy cannot be zero address"); + _delayedWethProxy = IDelayedWETH(payable(_value)); + } else if (_sel == this.anchorStateRegistryProxy.selector) { + require(_value != address(0), "DeployDisputeGame: anchorStateRegistryProxy cannot be zero address"); + _anchorStateRegistryProxy = IAnchorStateRegistry(payable(_value)); + } else if (_sel == this.proposer.selector) { + require(_value != address(0), "DeployDisputeGame: proposer cannot be zero address"); + _proposer = _value; + } else if (_sel == this.challenger.selector) { + require(_value != address(0), "DeployDisputeGame: challenger cannot be zero address"); + _challenger = _value; + } else { + revert("DeployDisputeGame: unknown selector"); + } + } + + function set(bytes4 _sel, string memory _value) public { + if (_sel == this.gameKind.selector) { + require( + LibString.eq(_value, "FaultDisputeGame") || LibString.eq(_value, "PermissionedDisputeGame"), + "DeployDisputeGame: unknown game kind" + ); + _gameKind = _value; + } else if (_sel == this.release.selector) { + require(!LibString.eq(_value, ""), "DeployDisputeGame: release cannot be empty"); + _release = _value; + } else if (_sel == this.standardVersionsToml.selector) { + require(!LibString.eq(_value, ""), "DeployDisputeGame: standardVersionsToml cannot be empty"); + _standardVersionsToml = _value; + } else { + revert("DeployDisputeGame: unknown selector"); + } + } + + function release() public view returns (string memory) { + require(!LibString.eq(_release, ""), "DeployDisputeGame: release not set"); + return _release; + } + + function standardVersionsToml() public view returns (string memory) { + require(!LibString.eq(_standardVersionsToml, ""), "DeployDisputeGame: standardVersionsToml not set"); + return _standardVersionsToml; + } + + function vmAddress() public view returns (IBigStepper) { + return _vm; + } + + function gameKind() public view returns (string memory) { + require( + LibString.eq(_gameKind, "FaultDisputeGame") || LibString.eq(_gameKind, "PermissionedDisputeGame"), + "DeployDisputeGame: unknown game kind" + ); + return _gameKind; + } + + function gameType() public view returns (uint256) { + require(_gameType <= type(uint32).max, "DeployDisputeGame: gameType must fit inside uint32"); + return _gameType; + } + + function absolutePrestate() public view returns (bytes32) { + require(_absolutePrestate != bytes32(0), "DeployDisputeGame: absolutePrestate not set"); + return _absolutePrestate; + } + + function maxGameDepth() public view returns (uint256) { + require(_maxGameDepth != 0, "DeployDisputeGame: maxGameDepth not set"); + return _maxGameDepth; + } + + function splitDepth() public view returns (uint256) { + require(_splitDepth != 0, "DeployDisputeGame: splitDepth not set"); + return _splitDepth; + } + + function clockExtension() public view returns (uint256) { + require(_clockExtension <= type(uint64).max, "DeployDisputeGame: clockExtension must fit inside uint64"); + require(_clockExtension != 0, "DeployDisputeGame: clockExtension not set"); + return _clockExtension; + } + + function maxClockDuration() public view returns (uint256) { + require(_maxClockDuration <= type(uint64).max, "DeployDisputeGame: maxClockDuration must fit inside uint64"); + require(_maxClockDuration != 0, "DeployDisputeGame: maxClockDuration not set"); + return _maxClockDuration; + } + + function delayedWethProxy() public view returns (IDelayedWETH) { + require(address(_delayedWethProxy) != address(0), "DeployDisputeGame: delayedWethProxy not set"); + return _delayedWethProxy; + } + + function anchorStateRegistryProxy() public view returns (IAnchorStateRegistry) { + require(address(_anchorStateRegistryProxy) != address(0), "DeployDisputeGame: anchorStateRegistryProxy not set"); + return _anchorStateRegistryProxy; + } + + function l2ChainId() public view returns (uint256) { + require(_l2ChainId != 0, "DeployDisputeGame: l2ChainId not set"); + return _l2ChainId; + } + + function proposer() public view returns (address) { + if (LibString.eq(_gameKind, "FaultDisputeGame")) { + require(_proposer == address(0), "DeployDisputeGame: proposer must be empty"); + } else { + require(_proposer != address(0), "DeployDisputeGame: proposer not set"); + } + return _proposer; + } + + function challenger() public view returns (address) { + if (LibString.eq(_gameKind, "FaultDisputeGame")) { + require(_challenger == address(0), "DeployDisputeGame: challenger must be empty"); + } else { + require(_challenger != address(0), "DeployDisputeGame: challenger not set"); + } + return _challenger; + } +} + +/// @title DeployDisputeGameOutput +contract DeployDisputeGameOutput is BaseDeployIO { + // PermissionedDisputeGame is used as the type here because it has all of the same functions as + // FaultDisputeGame but with the added proposer and challenger fields. + IPermissionedDisputeGame internal _disputeGameImpl; + + function set(bytes4 _sel, address _value) public { + if (_sel == this.disputeGameImpl.selector) { + require(_value != address(0), "DeployDisputeGame: disputeGameImpl cannot be zero address"); + _disputeGameImpl = IPermissionedDisputeGame(_value); + } else { + revert("DeployDisputeGame: unknown selector"); + } + } + + function checkOutput(DeployDisputeGameInput _dgi) public view { + DeployUtils.assertValidContractAddress(address(_disputeGameImpl)); + assertValidDeploy(_dgi); + } + + function disputeGameImpl() public view returns (IPermissionedDisputeGame) { + DeployUtils.assertValidContractAddress(address(_disputeGameImpl)); + return _disputeGameImpl; + } + + function assertValidDeploy(DeployDisputeGameInput _dgi) public view { + assertValidDisputeGameImpl(_dgi); + } + + function assertValidDisputeGameImpl(DeployDisputeGameInput _dgi) internal view { + IPermissionedDisputeGame game = disputeGameImpl(); + + require(game.gameType().raw() == uint32(_dgi.gameType()), "DG-10"); + require(game.maxGameDepth() == _dgi.maxGameDepth(), "DG-20"); + require(game.splitDepth() == _dgi.splitDepth(), "DG-30"); + require(game.clockExtension().raw() == uint64(_dgi.clockExtension()), "DG-40"); + require(game.maxClockDuration().raw() == uint64(_dgi.maxClockDuration()), "DG-50"); + require(game.vm() == _dgi.vmAddress(), "DG-60"); + require(game.weth() == _dgi.delayedWethProxy(), "DG-70"); + require(game.anchorStateRegistry() == _dgi.anchorStateRegistryProxy(), "DG-80"); + require(game.l2ChainId() == _dgi.l2ChainId(), "DG-90"); + + if (LibString.eq(_dgi.gameKind(), "PermissionedDisputeGame")) { + require(game.proposer() == _dgi.proposer(), "DG-100"); + require(game.challenger() == _dgi.challenger(), "DG-110"); + } + } +} + +/// @title DeployDisputeGame +contract DeployDisputeGame is Script { + /// We need a struct for constructor args to avoid stack-too-deep errors. + struct DisputeGameConstructorArgs { + GameType gameType; + Claim absolutePrestate; + uint256 maxGameDepth; + uint256 splitDepth; + Duration clockExtension; + Duration maxClockDuration; + IBigStepper gameVm; + IDelayedWETH delayedWethProxy; + IAnchorStateRegistry anchorStateRegistryProxy; + uint256 l2ChainId; + address proposer; + address challenger; + } + + function run(DeployDisputeGameInput _dgi, DeployDisputeGameOutput _dgo) public { + deployDisputeGameImpl(_dgi, _dgo); + _dgo.checkOutput(_dgi); + } + + function deployDisputeGameImpl(DeployDisputeGameInput _dgi, DeployDisputeGameOutput _dgo) internal { + // Shove the arguments into a struct to avoid stack-too-deep errors. + IFaultDisputeGame.GameConstructorParams memory args = IFaultDisputeGame.GameConstructorParams({ + gameType: GameType.wrap(uint32(_dgi.gameType())), + absolutePrestate: Claim.wrap(_dgi.absolutePrestate()), + maxGameDepth: _dgi.maxGameDepth(), + splitDepth: _dgi.splitDepth(), + clockExtension: Duration.wrap(uint64(_dgi.clockExtension())), + maxClockDuration: Duration.wrap(uint64(_dgi.maxClockDuration())), + vm: IBigStepper(address(_dgi.vmAddress())), + weth: _dgi.delayedWethProxy(), + anchorStateRegistry: _dgi.anchorStateRegistryProxy(), + l2ChainId: _dgi.l2ChainId() + }); + + // PermissionedDisputeGame is used as the type here because it is a superset of + // FaultDisputeGame. If the user requests to deploy a FaultDisputeGame, the user will get a + // FaultDisputeGame (and not a PermissionedDisputeGame). + vm.broadcast(msg.sender); + IPermissionedDisputeGame impl; + if (LibString.eq(_dgi.gameKind(), "FaultDisputeGame")) { + impl = IPermissionedDisputeGame( + DeployUtils.create1({ + _name: "FaultDisputeGame", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IFaultDisputeGame.__constructor__, (args))) + }) + ); + } else { + impl = IPermissionedDisputeGame( + DeployUtils.create1({ + _name: "PermissionedDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IPermissionedDisputeGame.__constructor__, (args, _dgi.proposer(), _dgi.challenger())) + ) + }) + ); + } + + vm.label(address(impl), string.concat(_dgi.gameKind(), "Impl")); + _dgo.set(_dgo.disputeGameImpl.selector, address(impl)); + } + + // Zero address is returned if the address is not found in '_standardVersionsToml'. + function getReleaseAddress( + string memory _version, + string memory _contractName, + string memory _standardVersionsToml + ) + internal + pure + returns (address addr_) + { + string memory baseKey = string.concat('.releases["', _version, '"].', _contractName); + string memory implAddressKey = string.concat(baseKey, ".implementation_address"); + string memory addressKey = string.concat(baseKey, ".address"); + try vm.parseTomlAddress(_standardVersionsToml, implAddressKey) returns (address parsedAddr_) { + addr_ = parsedAddr_; + } catch { + try vm.parseTomlAddress(_standardVersionsToml, addressKey) returns (address parsedAddr_) { + addr_ = parsedAddr_; + } catch { + addr_ = address(0); + } + } + } + + // A release is considered a 'develop' release if it does not start with 'op-contracts'. + function isDevelopRelease(string memory _release) internal pure returns (bool) { + return !LibString.startsWith(_release, "op-contracts"); + } +} diff --git a/packages/contracts-bedrock/scripts/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol similarity index 71% rename from packages/contracts-bedrock/scripts/DeployImplementations.s.sol rename to packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 3179ea3d6426e..716a95b071cf0 100644 --- a/packages/contracts-bedrock/scripts/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -5,41 +5,36 @@ import { Script } from "forge-std/Script.sol"; import { LibString } from "@solady/utils/LibString.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; -import { ISystemConfigV160 } from "src/L1/interfaces/ISystemConfigV160.sol"; -import { IL1CrossDomainMessengerV160 } from "src/L1/interfaces/IL1CrossDomainMessengerV160.sol"; -import { IL1StandardBridgeV160 } from "src/L1/interfaces/IL1StandardBridgeV160.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Bytes } from "src/libraries/Bytes.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; - -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; -import { IMIPS } from "src/cannon/interfaces/IMIPS.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { IMIPS } from "interfaces/cannon/IMIPS.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { OPContractsManagerInterop } from "src/L1/OPContractsManagerInterop.sol"; -import { IOptimismPortalInterop } from "src/L1/interfaces/IOptimismPortalInterop.sol"; -import { ISystemConfigInterop } from "src/L1/interfaces/ISystemConfigInterop.sol"; +import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; +import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; import { Blueprint } from "src/libraries/Blueprint.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; -import { BaseDeployIO } from "scripts/utils/BaseDeployIO.sol"; +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; // See DeploySuperchain.s.sol for detailed comments on the script architecture used here. contract DeployImplementationsInput is BaseDeployIO { @@ -51,8 +46,9 @@ contract DeployImplementationsInput is BaseDeployIO { uint256 internal _disputeGameFinalityDelaySeconds; uint256 internal _mipsVersion; - // The release version to set OPCM implementations for, of the format `op-contracts/vX.Y.Z`. - string internal _release; + // This is used in opcm to signal which version of the L1 smart contracts is deployed. + // It takes the format of `op-contracts/v*.*.*`. + string internal _l1ContractsRelease; // Outputs from DeploySuperchain.s.sol. ISuperchainConfig internal _superchainConfigProxy; @@ -60,8 +56,6 @@ contract DeployImplementationsInput is BaseDeployIO { string internal _standardVersionsToml; - address internal _opcmProxyOwner; - function set(bytes4 _sel, uint256 _value) public { require(_value != 0, "DeployImplementationsInput: cannot set zero value"); @@ -85,7 +79,7 @@ contract DeployImplementationsInput is BaseDeployIO { function set(bytes4 _sel, string memory _value) public { require(!LibString.eq(_value, ""), "DeployImplementationsInput: cannot set empty string"); - if (_sel == this.release.selector) _release = _value; + if (_sel == this.l1ContractsRelease.selector) _l1ContractsRelease = _value; else if (_sel == this.standardVersionsToml.selector) _standardVersionsToml = _value; else revert("DeployImplementationsInput: unknown selector"); } @@ -94,7 +88,6 @@ contract DeployImplementationsInput is BaseDeployIO { require(_addr != address(0), "DeployImplementationsInput: cannot set zero address"); if (_sel == this.superchainConfigProxy.selector) _superchainConfigProxy = ISuperchainConfig(_addr); else if (_sel == this.protocolVersionsProxy.selector) _protocolVersionsProxy = IProtocolVersions(_addr); - else if (_sel == this.opcmProxyOwner.selector) _opcmProxyOwner = _addr; else revert("DeployImplementationsInput: unknown selector"); } @@ -141,9 +134,9 @@ contract DeployImplementationsInput is BaseDeployIO { return _mipsVersion; } - function release() public view returns (string memory) { - require(!LibString.eq(_release, ""), "DeployImplementationsInput: not set"); - return _release; + function l1ContractsRelease() public view returns (string memory) { + require(!LibString.eq(_l1ContractsRelease, ""), "DeployImplementationsInput: not set"); + return _l1ContractsRelease; } function standardVersionsToml() public view returns (string memory) { @@ -160,16 +153,10 @@ contract DeployImplementationsInput is BaseDeployIO { require(address(_protocolVersionsProxy) != address(0), "DeployImplementationsInput: not set"); return _protocolVersionsProxy; } - - function opcmProxyOwner() public view returns (address) { - require(address(_opcmProxyOwner) != address(0), "DeployImplementationsInput: not set"); - return _opcmProxyOwner; - } } contract DeployImplementationsOutput is BaseDeployIO { - OPContractsManager internal _opcmProxy; - OPContractsManager internal _opcmImpl; + OPContractsManager internal _opcm; IDelayedWETH internal _delayedWETHImpl; IOptimismPortal2 internal _optimismPortalImpl; IPreimageOracle internal _preimageOracleSingleton; @@ -185,8 +172,7 @@ contract DeployImplementationsOutput is BaseDeployIO { require(_addr != address(0), "DeployImplementationsOutput: cannot set zero address"); // forgefmt: disable-start - if (_sel == this.opcmProxy.selector) _opcmProxy = OPContractsManager(payable(_addr)); - else if (_sel == this.opcmImpl.selector) _opcmImpl = OPContractsManager(payable(_addr)); + if (_sel == this.opcm.selector) _opcm = OPContractsManager(_addr); else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = IOptimismPortal2(payable(_addr)); else if (_sel == this.delayedWETHImpl.selector) _delayedWETHImpl = IDelayedWETH(payable(_addr)); else if (_sel == this.preimageOracleSingleton.selector) _preimageOracleSingleton = IPreimageOracle(_addr); @@ -201,12 +187,11 @@ contract DeployImplementationsOutput is BaseDeployIO { // forgefmt: disable-end } - function checkOutput(DeployImplementationsInput _dii) public { + function checkOutput(DeployImplementationsInput _dii) public view { // With 12 addresses, we'd get a stack too deep error if we tried to do this inline as a // single call to `Solarray.addresses`. So we split it into two calls. address[] memory addrs1 = Solarray.addresses( - address(this.opcmProxy()), - address(this.opcmImpl()), + address(this.opcm()), address(this.optimismPortalImpl()), address(this.delayedWETHImpl()), address(this.preimageOracleSingleton()), @@ -227,15 +212,9 @@ contract DeployImplementationsOutput is BaseDeployIO { assertValidDeploy(_dii); } - function opcmProxy() public returns (OPContractsManager) { - DeployUtils.assertValidContractAddress(address(_opcmProxy)); - DeployUtils.assertERC1967ImplementationSet(address(_opcmProxy)); - return _opcmProxy; - } - - function opcmImpl() public view returns (OPContractsManager) { - DeployUtils.assertValidContractAddress(address(_opcmImpl)); - return _opcmImpl; + function opcm() public view returns (OPContractsManager) { + DeployUtils.assertValidContractAddress(address(_opcm)); + return _opcm; } function optimismPortalImpl() public view returns (IOptimismPortal2) { @@ -289,40 +268,22 @@ contract DeployImplementationsOutput is BaseDeployIO { } // -------- Deployment Assertions -------- - function assertValidDeploy(DeployImplementationsInput _dii) public { + function assertValidDeploy(DeployImplementationsInput _dii) public view { assertValidDelayedWETHImpl(_dii); assertValidDisputeGameFactoryImpl(_dii); assertValidL1CrossDomainMessengerImpl(_dii); assertValidL1ERC721BridgeImpl(_dii); assertValidL1StandardBridgeImpl(_dii); assertValidMipsSingleton(_dii); - assertValidOpcmProxy(_dii); - assertValidOpcmImpl(_dii); + assertValidOpcm(_dii); assertValidOptimismMintableERC20FactoryImpl(_dii); assertValidOptimismPortalImpl(_dii); assertValidPreimageOracleSingleton(_dii); assertValidSystemConfigImpl(_dii); } - function assertValidOpcmProxy(DeployImplementationsInput _dii) internal { - // First we check the proxy as itself. - IProxy proxy = IProxy(payable(address(opcmProxy()))); - vm.prank(address(0)); - address admin = proxy.admin(); - require(admin == address(_dii.opcmProxyOwner()), "OPCMP-10"); - - // Then we check the proxy as OPCM. - DeployUtils.assertInitialized({ _contractAddress: address(opcmProxy()), _slot: 0, _offset: 0 }); - require(address(opcmProxy().superchainConfig()) == address(_dii.superchainConfigProxy()), "OPCMP-20"); - require(address(opcmProxy().protocolVersions()) == address(_dii.protocolVersionsProxy()), "OPCMP-30"); - require(LibString.eq(opcmProxy().latestRelease(), _dii.release()), "OPCMP-50"); // Initial release is latest. - } - - function assertValidOpcmImpl(DeployImplementationsInput _dii) internal { - IProxy proxy = IProxy(payable(address(opcmProxy()))); - vm.prank(address(0)); - OPContractsManager impl = OPContractsManager(proxy.implementation()); - DeployUtils.assertInitialized({ _contractAddress: address(impl), _slot: 0, _offset: 0 }); + function assertValidOpcm(DeployImplementationsInput _dii) internal view { + OPContractsManager impl = OPContractsManager(address(opcm())); require(address(impl.superchainConfig()) == address(_dii.superchainConfigProxy()), "OPCMI-10"); require(address(impl.protocolVersions()) == address(_dii.protocolVersionsProxy()), "OPCMI-20"); } @@ -361,7 +322,6 @@ contract DeployImplementationsOutput is BaseDeployIO { function assertValidMipsSingleton(DeployImplementationsInput) internal view { IMIPS mips = mipsSingleton(); - require(address(mips.oracle()) == address(preimageOracleSingleton()), "MIPS-10"); } @@ -480,104 +440,38 @@ contract DeployImplementations is Script { // --- OP Contracts Manager --- - function opcmSystemConfigSetter( - DeployImplementationsInput _dii, - DeployImplementationsOutput _dio - ) - internal - view - virtual - returns (OPContractsManager.ImplementationSetter memory) - { - // When configuring OPCM during Solidity tests, we are using the latest SystemConfig.sol - // version in this repo, which contains Custom Gas Token (CGT) features. This CGT version - // has a different `initialize` signature than the SystemConfig version that was released - // as part of `op-contracts/v1.6.0`, which is no longer in the repo. When running this - // script's bytecode for a production deploy of OPCM at `op-contracts/v1.6.0`, we need to - // use the ISystemConfigV160 interface instead of ISystemConfig. Therefore the selector used - // is a function of the `release` passed in by the caller. - bytes4 selector = LibString.eq(_dii.release(), "op-contracts/v1.6.0") - ? ISystemConfigV160.initialize.selector - : ISystemConfig.initialize.selector; - return OPContractsManager.ImplementationSetter({ - name: "SystemConfig", - info: OPContractsManager.Implementation(address(_dio.systemConfigImpl()), selector) - }); - } - - function l1CrossDomainMessengerConfigSetter( - DeployImplementationsInput _dii, - DeployImplementationsOutput _dio - ) - internal - view - virtual - returns (OPContractsManager.ImplementationSetter memory) - { - bytes4 selector = LibString.eq(_dii.release(), "op-contracts/v1.6.0") - ? IL1CrossDomainMessengerV160.initialize.selector - : IL1CrossDomainMessenger.initialize.selector; - return OPContractsManager.ImplementationSetter({ - name: "L1CrossDomainMessenger", - info: OPContractsManager.Implementation(address(_dio.l1CrossDomainMessengerImpl()), selector) - }); - } - - function l1StandardBridgeConfigSetter( - DeployImplementationsInput _dii, - DeployImplementationsOutput _dio - ) - internal - view - virtual - returns (OPContractsManager.ImplementationSetter memory) - { - bytes4 selector = LibString.eq(_dii.release(), "op-contracts/v1.6.0") - ? IL1StandardBridgeV160.initialize.selector - : IL1StandardBridge.initialize.selector; - return OPContractsManager.ImplementationSetter({ - name: "L1StandardBridge", - info: OPContractsManager.Implementation(address(_dio.l1StandardBridgeImpl()), selector) - }); - } - - // Deploy and initialize a proxied OPContractsManager. function createOPCMContract( DeployImplementationsInput _dii, DeployImplementationsOutput _dio, OPContractsManager.Blueprints memory _blueprints, - string memory _release, - OPContractsManager.ImplementationSetter[] memory _setters + string memory _l1ContractsRelease ) internal virtual - returns (OPContractsManager opcmProxy_) + returns (OPContractsManager opcm_) { - address opcmProxyOwner = _dii.opcmProxyOwner(); - - vm.broadcast(msg.sender); - IProxy proxy = IProxy( - DeployUtils.create1({ - _name: "Proxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender))) - }) - ); - - deployOPContractsManagerImpl(_dii, _dio); - OPContractsManager opcmImpl = _dio.opcmImpl(); + ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); + IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); - OPContractsManager.InitializerInputs memory initializerInputs = - OPContractsManager.InitializerInputs(_blueprints, _setters, _release, true); + OPContractsManager.Implementations memory implementations = OPContractsManager.Implementations({ + l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), + optimismPortalImpl: address(_dio.optimismPortalImpl()), + systemConfigImpl: address(_dio.systemConfigImpl()), + optimismMintableERC20FactoryImpl: address(_dio.optimismMintableERC20FactoryImpl()), + l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()), + l1StandardBridgeImpl: address(_dio.l1StandardBridgeImpl()), + disputeGameFactoryImpl: address(_dio.disputeGameFactoryImpl()), + delayedWETHImpl: address(_dio.delayedWETHImpl()), + mipsImpl: address(_dio.mipsSingleton()) + }); - vm.startBroadcast(msg.sender); - proxy.upgradeToAndCall( - address(opcmImpl), abi.encodeWithSelector(opcmImpl.initialize.selector, initializerInputs) + vm.broadcast(msg.sender); + opcm_ = new OPContractsManager( + superchainConfigProxy, protocolVersionsProxy, _l1ContractsRelease, _blueprints, implementations ); - proxy.changeAdmin(address(opcmProxyOwner)); // transfer ownership of Proxy contract to the ProxyAdmin contract - vm.stopBroadcast(); - - opcmProxy_ = OPContractsManager(address(proxy)); + vm.label(address(opcm_), "OPContractsManager"); + _dio.set(_dio.opcm.selector, address(opcm_)); } function deployOPContractsManager( @@ -587,72 +481,42 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); - - // First we deploy the blueprints for the singletons deployed by OPCM. - // forgefmt: disable-start - bytes32 salt = _dii.salt(); - OPContractsManager.Blueprints memory blueprints; - - vm.startBroadcast(msg.sender); - blueprints.addressManager = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("AddressManager")), salt); - blueprints.proxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("Proxy")), salt); - blueprints.proxyAdmin = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("ProxyAdmin")), salt); - blueprints.l1ChugSplashProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("L1ChugSplashProxy")), salt); - blueprints.resolvedDelegateProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("ResolvedDelegateProxy")), salt); - blueprints.anchorStateRegistry = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("AnchorStateRegistry")), salt); - (blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = deployBigBytecode(vm.getCode("PermissionedDisputeGame"), salt); - vm.stopBroadcast(); - // forgefmt: disable-end - - OPContractsManager.ImplementationSetter[] memory setters = new OPContractsManager.ImplementationSetter[](9); - setters[0] = OPContractsManager.ImplementationSetter({ - name: "L1ERC721Bridge", - info: OPContractsManager.Implementation(address(_dio.l1ERC721BridgeImpl()), IL1ERC721Bridge.initialize.selector) - }); - setters[1] = OPContractsManager.ImplementationSetter({ - name: "OptimismPortal", - info: OPContractsManager.Implementation( - address(_dio.optimismPortalImpl()), IOptimismPortal2.initialize.selector - ) - }); - setters[2] = opcmSystemConfigSetter(_dii, _dio); - setters[3] = OPContractsManager.ImplementationSetter({ - name: "OptimismMintableERC20Factory", - info: OPContractsManager.Implementation( - address(_dio.optimismMintableERC20FactoryImpl()), IOptimismMintableERC20Factory.initialize.selector - ) - }); - setters[4] = l1CrossDomainMessengerConfigSetter(_dii, _dio); - setters[5] = l1StandardBridgeConfigSetter(_dii, _dio); - setters[6] = OPContractsManager.ImplementationSetter({ - name: "DisputeGameFactory", - info: OPContractsManager.Implementation( - address(_dio.disputeGameFactoryImpl()), IDisputeGameFactory.initialize.selector - ) - }); - setters[7] = OPContractsManager.ImplementationSetter({ - name: "DelayedWETH", - info: OPContractsManager.Implementation(address(_dio.delayedWETHImpl()), IDelayedWETH.initialize.selector) - }); - setters[8] = OPContractsManager.ImplementationSetter({ - name: "MIPS", - // MIPS is a singleton for all chains, so it doesn't need to be initialized, so the - // selector is just `bytes4(0)`. - info: OPContractsManager.Implementation(address(_dio.mipsSingleton()), bytes4(0)) - }); + string memory l1ContractsRelease = _dii.l1ContractsRelease(); + string memory stdVerToml = _dii.standardVersionsToml(); + string memory contractName = "op_contracts_manager"; + OPContractsManager opcm; - // This call contains a broadcast to deploy OPCM which is proxied. - OPContractsManager opcmProxy = createOPCMContract(_dii, _dio, blueprints, release, setters); + address existingImplementation = getReleaseAddress(l1ContractsRelease, contractName, stdVerToml); + if (existingImplementation != address(0)) { + opcm = OPContractsManager(existingImplementation); + } else { + // First we deploy the blueprints for the singletons deployed by OPCM. + // forgefmt: disable-start + bytes32 salt = _dii.salt(); + OPContractsManager.Blueprints memory blueprints; + + vm.startBroadcast(msg.sender); + blueprints.addressManager = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("AddressManager")), salt); + blueprints.proxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("Proxy")), salt); + blueprints.proxyAdmin = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("ProxyAdmin")), salt); + blueprints.l1ChugSplashProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("L1ChugSplashProxy")), salt); + blueprints.resolvedDelegateProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("ResolvedDelegateProxy")), salt); + blueprints.anchorStateRegistry = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("AnchorStateRegistry")), salt); + (blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = deployBigBytecode(vm.getCode("PermissionedDisputeGame"), salt); + vm.stopBroadcast(); + // forgefmt: disable-end + + opcm = createOPCMContract(_dii, _dio, blueprints, l1ContractsRelease); + } - vm.label(address(opcmProxy), "OPContractsManager"); - _dio.set(_dio.opcmProxy.selector, address(opcmProxy)); + vm.label(address(opcm), "OPContractsManager"); + _dio.set(_dio.opcm.selector, address(opcm)); } // --- Core Contracts --- function deploySystemConfigImpl(DeployImplementationsInput _dii, DeployImplementationsOutput _dio) public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); // Using snake case for contract name to match the TOML file in superchain-registry. string memory contractName = "system_config"; @@ -661,7 +525,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = ISystemConfig(existingImplementation); - } else if (isDevelopRelease(release)) { + } else { // Deploy a new implementation for development builds. vm.broadcast(msg.sender); impl = ISystemConfig( @@ -670,8 +534,6 @@ contract DeployImplementations is Script { _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfig.__constructor__, ())) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "SystemConfigImpl"); @@ -685,7 +547,7 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "l1_cross_domain_messenger"; IL1CrossDomainMessenger impl; @@ -693,7 +555,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IL1CrossDomainMessenger(existingImplementation); - } else if (isDevelopRelease(release)) { + } else { vm.broadcast(msg.sender); impl = IL1CrossDomainMessenger( DeployUtils.create1({ @@ -701,8 +563,6 @@ contract DeployImplementations is Script { _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1CrossDomainMessenger.__constructor__, ())) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "L1CrossDomainMessengerImpl"); @@ -716,7 +576,7 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "l1_erc721_bridge"; IL1ERC721Bridge impl; @@ -724,7 +584,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IL1ERC721Bridge(existingImplementation); - } else if (isDevelopRelease(release)) { + } else { vm.broadcast(msg.sender); impl = IL1ERC721Bridge( DeployUtils.create1({ @@ -732,8 +592,6 @@ contract DeployImplementations is Script { _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ERC721Bridge.__constructor__, ())) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "L1ERC721BridgeImpl"); @@ -747,7 +605,7 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "l1_standard_bridge"; IL1StandardBridge impl; @@ -755,7 +613,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IL1StandardBridge(payable(existingImplementation)); - } else if (isDevelopRelease(release)) { + } else { vm.broadcast(msg.sender); impl = IL1StandardBridge( DeployUtils.create1({ @@ -763,8 +621,6 @@ contract DeployImplementations is Script { _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1StandardBridge.__constructor__, ())) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "L1StandardBridgeImpl"); @@ -778,7 +634,7 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "optimism_mintable_erc20_factory"; IOptimismMintableERC20Factory impl; @@ -786,7 +642,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IOptimismMintableERC20Factory(existingImplementation); - } else if (isDevelopRelease(release)) { + } else { vm.broadcast(msg.sender); impl = IOptimismMintableERC20Factory( DeployUtils.create1({ @@ -794,32 +650,12 @@ contract DeployImplementations is Script { _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismMintableERC20Factory.__constructor__, ())) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "OptimismMintableERC20FactoryImpl"); _dio.set(_dio.optimismMintableERC20FactoryImpl.selector, address(impl)); } - function deployOPContractsManagerImpl( - DeployImplementationsInput _dii, - DeployImplementationsOutput _dio - ) - public - virtual - { - ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); - IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); - - vm.broadcast(msg.sender); - // TODO: Eventually we will want to select the correct implementation based on the release. - OPContractsManager impl = new OPContractsManager(superchainConfigProxy, protocolVersionsProxy); - - vm.label(address(impl), "OPContractsManagerImpl"); - _dio.set(_dio.opcmImpl.selector, address(impl)); - } - // --- Fault Proofs Contracts --- // The fault proofs contracts are configured as follows: @@ -864,7 +700,7 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "optimism_portal"; IOptimismPortal2 impl; @@ -872,7 +708,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IOptimismPortal2(payable(existingImplementation)); - } else if (isDevelopRelease(release)) { + } else { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); vm.broadcast(msg.sender); @@ -886,8 +722,6 @@ contract DeployImplementations is Script { ) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "OptimismPortalImpl"); @@ -895,7 +729,7 @@ contract DeployImplementations is Script { } function deployDelayedWETHImpl(DeployImplementationsInput _dii, DeployImplementationsOutput _dio) public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "delayed_weth"; IDelayedWETH impl; @@ -903,7 +737,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IDelayedWETH(payable(existingImplementation)); - } else if (isDevelopRelease(release)) { + } else { uint256 withdrawalDelaySeconds = _dii.withdrawalDelaySeconds(); vm.broadcast(msg.sender); impl = IDelayedWETH( @@ -914,8 +748,6 @@ contract DeployImplementations is Script { ) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "DelayedWETHImpl"); @@ -929,7 +761,7 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "preimage_oracle"; IPreimageOracle singleton; @@ -937,7 +769,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { singleton = IPreimageOracle(payable(existingImplementation)); - } else if (isDevelopRelease(release)) { + } else { uint256 minProposalSizeBytes = _dii.minProposalSizeBytes(); uint256 challengePeriodSeconds = _dii.challengePeriodSeconds(); vm.broadcast(msg.sender); @@ -949,8 +781,6 @@ contract DeployImplementations is Script { ) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(singleton), "PreimageOracleSingleton"); @@ -958,7 +788,7 @@ contract DeployImplementations is Script { } function deployMipsSingleton(DeployImplementationsInput _dii, DeployImplementationsOutput _dio) public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "mips"; IMIPS singleton; @@ -966,18 +796,16 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { singleton = IMIPS(payable(existingImplementation)); - } else if (isDevelopRelease(release)) { + } else { uint256 mipsVersion = _dii.mipsVersion(); IPreimageOracle preimageOracle = IPreimageOracle(address(_dio.preimageOracleSingleton())); vm.broadcast(msg.sender); singleton = IMIPS( DeployUtils.create1({ - _name: mipsVersion == 1 ? "MIPS" : "MIPS2", + _name: mipsVersion == 1 ? "MIPS" : "MIPS64", _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS.__constructor__, (preimageOracle))) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(singleton), "MIPSSingleton"); @@ -991,7 +819,7 @@ contract DeployImplementations is Script { public virtual { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "dispute_game_factory"; IDisputeGameFactory impl; @@ -999,7 +827,7 @@ contract DeployImplementations is Script { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IDisputeGameFactory(payable(existingImplementation)); - } else if (isDevelopRelease(release)) { + } else { vm.broadcast(msg.sender); impl = IDisputeGameFactory( DeployUtils.create1({ @@ -1007,8 +835,6 @@ contract DeployImplementations is Script { _args: DeployUtils.encodeConstructor(abi.encodeCall(IDisputeGameFactory.__constructor__, ())) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "DisputeGameFactoryImpl"); @@ -1078,11 +904,6 @@ contract DeployImplementations is Script { } } } - - // A release is considered a 'develop' release if it does not start with 'op-contracts'. - function isDevelopRelease(string memory _release) internal pure returns (bool) { - return !LibString.startsWith(_release, "op-contracts"); - } } // Similar to how DeploySuperchain.s.sol contains a lot of comments to thoroughly document the script @@ -1122,38 +943,35 @@ contract DeployImplementationsInterop is DeployImplementations { DeployImplementationsInput _dii, DeployImplementationsOutput _dio, OPContractsManager.Blueprints memory _blueprints, - string memory _release, - OPContractsManager.ImplementationSetter[] memory _setters + string memory _l1ContractsRelease ) internal + virtual override - returns (OPContractsManager opcmProxy_) + returns (OPContractsManager opcm_) { - address opcmProxyOwner = _dii.opcmProxyOwner(); - - vm.broadcast(msg.sender); - IProxy proxy = IProxy( - DeployUtils.create1({ - _name: "Proxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender))) - }) - ); - - deployOPContractsManagerImpl(_dii, _dio); // overriding function - OPContractsManager opcmImpl = _dio.opcmImpl(); + ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); + IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); - OPContractsManager.InitializerInputs memory initializerInputs = - OPContractsManager.InitializerInputs(_blueprints, _setters, _release, true); + OPContractsManager.Implementations memory implementations = OPContractsManager.Implementations({ + l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), + optimismPortalImpl: address(_dio.optimismPortalImpl()), + systemConfigImpl: address(_dio.systemConfigImpl()), + optimismMintableERC20FactoryImpl: address(_dio.optimismMintableERC20FactoryImpl()), + l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()), + l1StandardBridgeImpl: address(_dio.l1StandardBridgeImpl()), + disputeGameFactoryImpl: address(_dio.disputeGameFactoryImpl()), + delayedWETHImpl: address(_dio.delayedWETHImpl()), + mipsImpl: address(_dio.mipsSingleton()) + }); - vm.startBroadcast(msg.sender); - proxy.upgradeToAndCall( - address(opcmImpl), abi.encodeWithSelector(opcmImpl.initialize.selector, initializerInputs) + vm.broadcast(msg.sender); + opcm_ = new OPContractsManagerInterop( + superchainConfigProxy, protocolVersionsProxy, _l1ContractsRelease, _blueprints, implementations ); - proxy.changeAdmin(opcmProxyOwner); // transfer ownership of Proxy contract to the ProxyAdmin contract - vm.stopBroadcast(); - - opcmProxy_ = OPContractsManagerInterop(address(proxy)); + vm.label(address(opcm_), "OPContractsManager"); + _dio.set(_dio.opcm.selector, address(opcm_)); } function deployOptimismPortalImpl( @@ -1163,7 +981,7 @@ contract DeployImplementationsInterop is DeployImplementations { public override { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "optimism_portal"; IOptimismPortalInterop impl; @@ -1171,7 +989,7 @@ contract DeployImplementationsInterop is DeployImplementations { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = IOptimismPortalInterop(payable(existingImplementation)); - } else if (isDevelopRelease(release)) { + } else { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); vm.broadcast(msg.sender); @@ -1186,8 +1004,6 @@ contract DeployImplementationsInterop is DeployImplementations { ) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "OptimismPortalImpl"); @@ -1201,7 +1017,7 @@ contract DeployImplementationsInterop is DeployImplementations { public override { - string memory release = _dii.release(); + string memory release = _dii.l1ContractsRelease(); string memory stdVerToml = _dii.standardVersionsToml(); string memory contractName = "system_config"; @@ -1210,7 +1026,7 @@ contract DeployImplementationsInterop is DeployImplementations { address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); if (existingImplementation != address(0)) { impl = ISystemConfigInterop(existingImplementation); - } else if (isDevelopRelease(release)) { + } else { vm.broadcast(msg.sender); impl = ISystemConfigInterop( DeployUtils.create1({ @@ -1218,46 +1034,9 @@ contract DeployImplementationsInterop is DeployImplementations { _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfigInterop.__constructor__, ())) }) ); - } else { - revert(string.concat("DeployImplementations: failed to deploy release ", release)); } vm.label(address(impl), "SystemConfigImpl"); _dio.set(_dio.systemConfigImpl.selector, address(impl)); } - - function deployOPContractsManagerImpl( - DeployImplementationsInput _dii, - DeployImplementationsOutput _dio - ) - public - override - { - ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); - IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); - - vm.broadcast(msg.sender); - // TODO: Eventually we will want to select the correct implementation based on the release. - OPContractsManager impl = new OPContractsManagerInterop(superchainConfigProxy, protocolVersionsProxy); - - vm.label(address(impl), "OPContractsManagerImpl"); - _dio.set(_dio.opcmImpl.selector, address(impl)); - } - - function opcmSystemConfigSetter( - DeployImplementationsInput, - DeployImplementationsOutput _dio - ) - internal - view - override - returns (OPContractsManager.ImplementationSetter memory) - { - return OPContractsManager.ImplementationSetter({ - name: "SystemConfig", - info: OPContractsManager.Implementation( - address(_dio.systemConfigImpl()), ISystemConfigInterop.initialize.selector - ) - }); - } } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployMIPS.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployMIPS.s.sol new file mode 100644 index 0000000000000..4c32be8128693 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployMIPS.s.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Forge +import { Script } from "forge-std/Script.sol"; + +// Scripts +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Interfaces +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { IMIPS } from "interfaces/cannon/IMIPS.sol"; + +/// @title DeployMIPSInput +contract DeployMIPSInput is BaseDeployIO { + // Specify the PreimageOracle to use + address internal _preimageOracle; + + // Specify which MIPS version to use. + uint256 internal _mipsVersion; + + function set(bytes4 _sel, uint256 _value) public { + if (_sel == this.mipsVersion.selector) { + require(_value == 1 || _value == 2, "DeployMIPS: unknown mips version"); + _mipsVersion = _value; + } else { + revert("DeployMIPS: unknown selector"); + } + } + + function set(bytes4 _sel, address _value) public { + if (_sel == this.preimageOracle.selector) { + require(_value != address(0), "DeployMIPS: preimageOracle cannot be empty"); + _preimageOracle = _value; + } else { + revert("DeployMIPS: unknown selector"); + } + } + + function mipsVersion() public view returns (uint256) { + require(_mipsVersion != 0, "DeployMIPS: mipsVersion not set"); + require(_mipsVersion == 1 || _mipsVersion == 2, "DeployMIPS: unknown mips version"); + return _mipsVersion; + } + + function preimageOracle() public view returns (address) { + require(_preimageOracle != address(0), "DeployMIPS: preimageOracle not set"); + return _preimageOracle; + } +} + +/// @title DeployMIPSOutput +contract DeployMIPSOutput is BaseDeployIO { + IMIPS internal _mipsSingleton; + + function set(bytes4 _sel, address _value) public { + if (_sel == this.mipsSingleton.selector) { + require(_value != address(0), "DeployMIPS: mipsSingleton cannot be zero address"); + _mipsSingleton = IMIPS(_value); + } else { + revert("DeployMIPS: unknown selector"); + } + } + + function checkOutput(DeployMIPSInput _mi) public view { + DeployUtils.assertValidContractAddress(address(_mipsSingleton)); + assertValidDeploy(_mi); + } + + function mipsSingleton() public view returns (IMIPS) { + DeployUtils.assertValidContractAddress(address(_mipsSingleton)); + return _mipsSingleton; + } + + function assertValidDeploy(DeployMIPSInput _mi) public view { + assertValidMipsSingleton(_mi); + } + + function assertValidMipsSingleton(DeployMIPSInput _mi) internal view { + IMIPS mips = mipsSingleton(); + + require(address(mips.oracle()) == address(_mi.preimageOracle()), "MIPS-10"); + } +} + +/// @title DeployMIPS +contract DeployMIPS is Script { + function run(DeployMIPSInput _mi, DeployMIPSOutput _mo) public { + deployMipsSingleton(_mi, _mo); + _mo.checkOutput(_mi); + } + + function deployMipsSingleton(DeployMIPSInput _mi, DeployMIPSOutput _mo) internal { + IMIPS singleton; + uint256 mipsVersion = _mi.mipsVersion(); + IPreimageOracle preimageOracle = IPreimageOracle(_mi.preimageOracle()); + vm.broadcast(msg.sender); + singleton = IMIPS( + DeployUtils.create1({ + _name: mipsVersion == 1 ? "MIPS" : "MIPS64", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS.__constructor__, (preimageOracle))) + }) + ); + + vm.label(address(singleton), "MIPSSingleton"); + _mo.set(_mo.mipsSingleton.selector, address(singleton)); + } +} diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol new file mode 100644 index 0000000000000..015cb78c75942 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Script } from "forge-std/Script.sol"; + +import { LibString } from "@solady/utils/LibString.sol"; + +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { OPContractsManager } from "src/L1/OPContractsManager.sol"; + +contract DeployOPCMInput is BaseDeployIO { + ISuperchainConfig internal _superchainConfig; + IProtocolVersions internal _protocolVersions; + string internal _l1ContractsRelease; + + address internal _addressManagerBlueprint; + address internal _proxyBlueprint; + address internal _proxyAdminBlueprint; + address internal _l1ChugSplashProxyBlueprint; + address internal _resolvedDelegateProxyBlueprint; + address internal _anchorStateRegistryBlueprint; + address internal _permissionedDisputeGame1Blueprint; + address internal _permissionedDisputeGame2Blueprint; + + address internal _l1ERC721BridgeImpl; + address internal _optimismPortalImpl; + address internal _systemConfigImpl; + address internal _optimismMintableERC20FactoryImpl; + address internal _l1CrossDomainMessengerImpl; + address internal _l1StandardBridgeImpl; + address internal _disputeGameFactoryImpl; + address internal _delayedWETHImpl; + address internal _mipsImpl; + + // Setter for address type + function set(bytes4 _sel, address _addr) public { + require(_addr != address(0), "DeployOPCMInput: cannot set zero address"); + + if (_sel == this.superchainConfig.selector) _superchainConfig = ISuperchainConfig(_addr); + else if (_sel == this.protocolVersions.selector) _protocolVersions = IProtocolVersions(_addr); + else if (_sel == this.addressManagerBlueprint.selector) _addressManagerBlueprint = _addr; + else if (_sel == this.proxyBlueprint.selector) _proxyBlueprint = _addr; + else if (_sel == this.proxyAdminBlueprint.selector) _proxyAdminBlueprint = _addr; + else if (_sel == this.l1ChugSplashProxyBlueprint.selector) _l1ChugSplashProxyBlueprint = _addr; + else if (_sel == this.resolvedDelegateProxyBlueprint.selector) _resolvedDelegateProxyBlueprint = _addr; + else if (_sel == this.anchorStateRegistryBlueprint.selector) _anchorStateRegistryBlueprint = _addr; + else if (_sel == this.permissionedDisputeGame1Blueprint.selector) _permissionedDisputeGame1Blueprint = _addr; + else if (_sel == this.permissionedDisputeGame2Blueprint.selector) _permissionedDisputeGame2Blueprint = _addr; + else if (_sel == this.l1ERC721BridgeImpl.selector) _l1ERC721BridgeImpl = _addr; + else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = _addr; + else if (_sel == this.systemConfigImpl.selector) _systemConfigImpl = _addr; + else if (_sel == this.optimismMintableERC20FactoryImpl.selector) _optimismMintableERC20FactoryImpl = _addr; + else if (_sel == this.l1CrossDomainMessengerImpl.selector) _l1CrossDomainMessengerImpl = _addr; + else if (_sel == this.l1StandardBridgeImpl.selector) _l1StandardBridgeImpl = _addr; + else if (_sel == this.disputeGameFactoryImpl.selector) _disputeGameFactoryImpl = _addr; + else if (_sel == this.delayedWETHImpl.selector) _delayedWETHImpl = _addr; + else if (_sel == this.mipsImpl.selector) _mipsImpl = _addr; + else revert("DeployOPCMInput: unknown selector"); + } + + // Setter for string type + function set(bytes4 _sel, string memory _value) public { + require(!LibString.eq(_value, ""), "DeployOPCMInput: cannot set empty string"); + if (_sel == this.l1ContractsRelease.selector) _l1ContractsRelease = _value; + else revert("DeployOPCMInput: unknown selector"); + } + + // Getters + function superchainConfig() public view returns (ISuperchainConfig) { + require(address(_superchainConfig) != address(0), "DeployOPCMInput: not set"); + return _superchainConfig; + } + + function protocolVersions() public view returns (IProtocolVersions) { + require(address(_protocolVersions) != address(0), "DeployOPCMInput: not set"); + return _protocolVersions; + } + + function l1ContractsRelease() public view returns (string memory) { + require(!LibString.eq(_l1ContractsRelease, ""), "DeployOPCMInput: not set"); + return _l1ContractsRelease; + } + + function addressManagerBlueprint() public view returns (address) { + require(_addressManagerBlueprint != address(0), "DeployOPCMInput: not set"); + return _addressManagerBlueprint; + } + + function proxyBlueprint() public view returns (address) { + require(_proxyBlueprint != address(0), "DeployOPCMInput: not set"); + return _proxyBlueprint; + } + + function proxyAdminBlueprint() public view returns (address) { + require(_proxyAdminBlueprint != address(0), "DeployOPCMInput: not set"); + return _proxyAdminBlueprint; + } + + function l1ChugSplashProxyBlueprint() public view returns (address) { + require(_l1ChugSplashProxyBlueprint != address(0), "DeployOPCMInput: not set"); + return _l1ChugSplashProxyBlueprint; + } + + function resolvedDelegateProxyBlueprint() public view returns (address) { + require(_resolvedDelegateProxyBlueprint != address(0), "DeployOPCMInput: not set"); + return _resolvedDelegateProxyBlueprint; + } + + function anchorStateRegistryBlueprint() public view returns (address) { + require(_anchorStateRegistryBlueprint != address(0), "DeployOPCMInput: not set"); + return _anchorStateRegistryBlueprint; + } + + function permissionedDisputeGame1Blueprint() public view returns (address) { + require(_permissionedDisputeGame1Blueprint != address(0), "DeployOPCMInput: not set"); + return _permissionedDisputeGame1Blueprint; + } + + function permissionedDisputeGame2Blueprint() public view returns (address) { + require(_permissionedDisputeGame2Blueprint != address(0), "DeployOPCMInput: not set"); + return _permissionedDisputeGame2Blueprint; + } + + function l1ERC721BridgeImpl() public view returns (address) { + require(_l1ERC721BridgeImpl != address(0), "DeployOPCMInput: not set"); + return _l1ERC721BridgeImpl; + } + + function optimismPortalImpl() public view returns (address) { + require(_optimismPortalImpl != address(0), "DeployOPCMInput: not set"); + return _optimismPortalImpl; + } + + function systemConfigImpl() public view returns (address) { + require(_systemConfigImpl != address(0), "DeployOPCMInput: not set"); + return _systemConfigImpl; + } + + function optimismMintableERC20FactoryImpl() public view returns (address) { + require(_optimismMintableERC20FactoryImpl != address(0), "DeployOPCMInput: not set"); + return _optimismMintableERC20FactoryImpl; + } + + function l1CrossDomainMessengerImpl() public view returns (address) { + require(_l1CrossDomainMessengerImpl != address(0), "DeployOPCMInput: not set"); + return _l1CrossDomainMessengerImpl; + } + + function l1StandardBridgeImpl() public view returns (address) { + require(_l1StandardBridgeImpl != address(0), "DeployOPCMInput: not set"); + return _l1StandardBridgeImpl; + } + + function disputeGameFactoryImpl() public view returns (address) { + require(_disputeGameFactoryImpl != address(0), "DeployOPCMInput: not set"); + return _disputeGameFactoryImpl; + } + + function delayedWETHImpl() public view returns (address) { + require(_delayedWETHImpl != address(0), "DeployOPCMInput: not set"); + return _delayedWETHImpl; + } + + function mipsImpl() public view returns (address) { + require(_mipsImpl != address(0), "DeployOPCMInput: not set"); + return _mipsImpl; + } +} + +contract DeployOPCMOutput is BaseDeployIO { + OPContractsManager internal _opcm; + + // Setter for address type + function set(bytes4 _sel, address _addr) public { + require(_addr != address(0), "DeployOPCMOutput: cannot set zero address"); + if (_sel == this.opcm.selector) _opcm = OPContractsManager(_addr); + else revert("DeployOPCMOutput: unknown selector"); + } + + // Getter + function opcm() public view returns (OPContractsManager) { + require(address(_opcm) != address(0), "DeployOPCMOutput: not set"); + return _opcm; + } +} + +contract DeployOPCM is Script { + function run(DeployOPCMInput _doi, DeployOPCMOutput _doo) public { + OPContractsManager.Blueprints memory blueprints = OPContractsManager.Blueprints({ + addressManager: _doi.addressManagerBlueprint(), + proxy: _doi.proxyBlueprint(), + proxyAdmin: _doi.proxyAdminBlueprint(), + l1ChugSplashProxy: _doi.l1ChugSplashProxyBlueprint(), + resolvedDelegateProxy: _doi.resolvedDelegateProxyBlueprint(), + anchorStateRegistry: _doi.anchorStateRegistryBlueprint(), + permissionedDisputeGame1: _doi.permissionedDisputeGame1Blueprint(), + permissionedDisputeGame2: _doi.permissionedDisputeGame2Blueprint() + }); + OPContractsManager.Implementations memory implementations = OPContractsManager.Implementations({ + l1ERC721BridgeImpl: address(_doi.l1ERC721BridgeImpl()), + optimismPortalImpl: address(_doi.optimismPortalImpl()), + systemConfigImpl: address(_doi.systemConfigImpl()), + optimismMintableERC20FactoryImpl: address(_doi.optimismMintableERC20FactoryImpl()), + l1CrossDomainMessengerImpl: address(_doi.l1CrossDomainMessengerImpl()), + l1StandardBridgeImpl: address(_doi.l1StandardBridgeImpl()), + disputeGameFactoryImpl: address(_doi.disputeGameFactoryImpl()), + delayedWETHImpl: address(_doi.delayedWETHImpl()), + mipsImpl: address(_doi.mipsImpl()) + }); + + OPContractsManager opcm_ = deployOPCM( + _doi.superchainConfig(), _doi.protocolVersions(), blueprints, implementations, _doi.l1ContractsRelease() + ); + _doo.set(_doo.opcm.selector, address(opcm_)); + + assertValidOpcm(_doi, _doo); + } + + function deployOPCM( + ISuperchainConfig _superchainConfig, + IProtocolVersions _protocolVersions, + OPContractsManager.Blueprints memory _blueprints, + OPContractsManager.Implementations memory _implementations, + string memory _l1ContractsRelease + ) + public + returns (OPContractsManager opcm_) + { + vm.broadcast(msg.sender); + opcm_ = new OPContractsManager( + _superchainConfig, _protocolVersions, _l1ContractsRelease, _blueprints, _implementations + ); + vm.label(address(opcm_), "OPContractsManager"); + } + + function assertValidOpcm(DeployOPCMInput _doi, DeployOPCMOutput _doo) public view { + OPContractsManager impl = OPContractsManager(address(_doo.opcm())); + require(address(impl.superchainConfig()) == address(_doi.superchainConfig()), "OPCMI-10"); + require(address(impl.protocolVersions()) == address(_doi.protocolVersions()), "OPCMI-20"); + require(LibString.eq(impl.l1ContractsRelease(), _doi.l1ContractsRelease())); + + OPContractsManager.Blueprints memory blueprints = impl.blueprints(); + require(blueprints.addressManager == _doi.addressManagerBlueprint(), "OPCMI-40"); + require(blueprints.proxy == _doi.proxyBlueprint(), "OPCMI-50"); + require(blueprints.proxyAdmin == _doi.proxyAdminBlueprint(), "OPCMI-60"); + require(blueprints.l1ChugSplashProxy == _doi.l1ChugSplashProxyBlueprint(), "OPCMI-70"); + require(blueprints.resolvedDelegateProxy == _doi.resolvedDelegateProxyBlueprint(), "OPCMI-80"); + require(blueprints.anchorStateRegistry == _doi.anchorStateRegistryBlueprint(), "OPCMI-90"); + require(blueprints.permissionedDisputeGame1 == _doi.permissionedDisputeGame1Blueprint(), "OPCMI-100"); + require(blueprints.permissionedDisputeGame2 == _doi.permissionedDisputeGame2Blueprint(), "OPCMI-110"); + + OPContractsManager.Implementations memory implementations = impl.implementations(); + require(implementations.l1ERC721BridgeImpl == _doi.l1ERC721BridgeImpl(), "OPCMI-120"); + require(implementations.optimismPortalImpl == _doi.optimismPortalImpl(), "OPCMI-130"); + require(implementations.systemConfigImpl == _doi.systemConfigImpl(), "OPCMI-140"); + require( + implementations.optimismMintableERC20FactoryImpl == _doi.optimismMintableERC20FactoryImpl(), "OPCMI-150" + ); + require(implementations.l1CrossDomainMessengerImpl == _doi.l1CrossDomainMessengerImpl(), "OPCMI-160"); + require(implementations.l1StandardBridgeImpl == _doi.l1StandardBridgeImpl(), "OPCMI-170"); + require(implementations.disputeGameFactoryImpl == _doi.disputeGameFactoryImpl(), "OPCMI-180"); + require(implementations.delayedWETHImpl == _doi.delayedWETHImpl(), "OPCMI-190"); + require(implementations.mipsImpl == _doi.mipsImpl(), "OPCMI-200"); + } + + function etchIOContracts() public returns (DeployOPCMInput doi_, DeployOPCMOutput doo_) { + (doi_, doo_) = getIOContracts(); + vm.etch(address(doi_), type(DeployOPCMInput).runtimeCode); + vm.etch(address(doo_), type(DeployOPCMOutput).runtimeCode); + } + + function getIOContracts() public view returns (DeployOPCMInput doi_, DeployOPCMOutput doo_) { + doi_ = DeployOPCMInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployOPCMInput")); + doo_ = DeployOPCMOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployOPCMOutput")); + } +} diff --git a/packages/contracts-bedrock/scripts/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol similarity index 74% rename from packages/contracts-bedrock/scripts/DeployOPChain.s.sol rename to packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index f88469b7ead53..cd0aa6d29f683 100644 --- a/packages/contracts-bedrock/scripts/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -7,33 +7,33 @@ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; -import { BaseDeployIO } from "scripts/utils/BaseDeployIO.sol"; +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Constants as ScriptConstants } from "scripts/libraries/Constants.sol"; -import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IPermissionedDisputeGame } from "src/dispute/interfaces/IPermissionedDisputeGame.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; import { Claim, Duration, GameType, GameTypes, Hash } from "src/dispute/lib/Types.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; contract DeployOPChainInput is BaseDeployIO { address internal _opChainProxyAdminOwner; @@ -47,7 +47,7 @@ contract DeployOPChainInput is BaseDeployIO { uint32 internal _basefeeScalar; uint32 internal _blobBaseFeeScalar; uint256 internal _l2ChainId; - OPContractsManager internal _opcmProxy; + OPContractsManager internal _opcm; string internal _saltMixer; uint64 internal _gasLimit; @@ -58,6 +58,7 @@ contract DeployOPChainInput is BaseDeployIO { uint256 internal _disputeSplitDepth; Duration internal _disputeClockExtension; Duration internal _disputeMaxClockDuration; + bool internal _allowCustomDisputeParameters; function set(bytes4 _sel, address _addr) public { require(_addr != address(0), "DeployOPChainInput: cannot set zero address"); @@ -67,7 +68,7 @@ contract DeployOPChainInput is BaseDeployIO { else if (_sel == this.unsafeBlockSigner.selector) _unsafeBlockSigner = _addr; else if (_sel == this.proposer.selector) _proposer = _addr; else if (_sel == this.challenger.selector) _challenger = _addr; - else if (_sel == this.opcmProxy.selector) _opcmProxy = OPContractsManager(_addr); + else if (_sel == this.opcm.selector) _opcm = OPContractsManager(_addr); else revert("DeployOPChainInput: unknown selector"); } @@ -107,6 +108,11 @@ contract DeployOPChainInput is BaseDeployIO { else revert("DeployImplementationsInput: unknown selector"); } + function set(bytes4 _sel, bool _value) public { + if (_sel == this.allowCustomDisputeParameters.selector) _allowCustomDisputeParameters = _value; + else revert("DeployOPChainInput: unknown selector"); + } + function opChainProxyAdminOwner() public view returns (address) { require(_opChainProxyAdminOwner != address(0), "DeployOPChainInput: not set"); return _opChainProxyAdminOwner; @@ -168,11 +174,10 @@ contract DeployOPChainInput is BaseDeployIO { return abi.encode(ScriptConstants.DEFAULT_STARTING_ANCHOR_ROOTS()); } - function opcmProxy() public returns (OPContractsManager) { - require(address(_opcmProxy) != address(0), "DeployOPChainInput: not set"); - DeployUtils.assertValidContractAddress(address(_opcmProxy)); - DeployUtils.assertERC1967ImplementationSet(address(_opcmProxy)); - return _opcmProxy; + function opcm() public view returns (OPContractsManager) { + require(address(_opcm) != address(0), "DeployOPChainInput: not set"); + DeployUtils.assertValidContractAddress(address(_opcm)); + return _opcm; } function saltMixer() public view returns (string memory) { @@ -206,6 +211,10 @@ contract DeployOPChainInput is BaseDeployIO { function disputeMaxClockDuration() public view returns (Duration) { return _disputeMaxClockDuration; } + + function allowCustomDisputeParameters() public view returns (bool) { + return _allowCustomDisputeParameters; + } } contract DeployOPChainOutput is BaseDeployIO { @@ -225,7 +234,7 @@ contract DeployOPChainOutput is BaseDeployIO { IDelayedWETH internal _delayedWETHPermissionedGameProxy; IDelayedWETH internal _delayedWETHPermissionlessGameProxy; - function set(bytes4 _sel, address _addr) public { + function set(bytes4 _sel, address _addr) public virtual { require(_addr != address(0), "DeployOPChainOutput: cannot set zero address"); // forgefmt: disable-start if (_sel == this.opChainProxyAdmin.selector) _opChainProxyAdmin = IProxyAdmin(_addr) ; @@ -247,35 +256,6 @@ contract DeployOPChainOutput is BaseDeployIO { // forgefmt: disable-end } - function checkOutput(DeployOPChainInput _doi) public { - // With 16 addresses, we'd get a stack too deep error if we tried to do this inline as a - // single call to `Solarray.addresses`. So we split it into two calls. - address[] memory addrs1 = Solarray.addresses( - address(_opChainProxyAdmin), - address(_addressManager), - address(_l1ERC721BridgeProxy), - address(_systemConfigProxy), - address(_optimismMintableERC20FactoryProxy), - address(_l1StandardBridgeProxy), - address(_l1CrossDomainMessengerProxy) - ); - address[] memory addrs2 = Solarray.addresses( - address(_optimismPortalProxy), - address(_disputeGameFactoryProxy), - address(_anchorStateRegistryProxy), - address(_anchorStateRegistryImpl), - // address(_faultDisputeGame), - address(_permissionedDisputeGame), - address(_delayedWETHPermissionedGameProxy) - ); - // TODO: Eventually switch from Permissioned to Permissionless. Add this address back in. - // address(_delayedWETHPermissionlessGameProxy) - - DeployUtils.assertValidContractAddresses(Solarray.extend(addrs1, addrs2)); - - assertValidDeploy(_doi); - } - function opChainProxyAdmin() public view returns (IProxyAdmin) { DeployUtils.assertValidContractAddress(address(_opChainProxyAdmin)); return _opChainProxyAdmin; @@ -360,29 +340,137 @@ contract DeployOPChainOutput is BaseDeployIO { // DeployUtils.assertValidContractAddress(address(_delayedWETHPermissionlessGameProxy)); return _delayedWETHPermissionlessGameProxy; } +} - // -------- Deployment Assertions -------- +contract DeployOPChain is Script { + // -------- Core Deployment Methods -------- + + function run(DeployOPChainInput _doi, DeployOPChainOutput _doo) public { + OPContractsManager opcm = _doi.opcm(); + + OPContractsManager.Roles memory roles = OPContractsManager.Roles({ + opChainProxyAdminOwner: _doi.opChainProxyAdminOwner(), + systemConfigOwner: _doi.systemConfigOwner(), + batcher: _doi.batcher(), + unsafeBlockSigner: _doi.unsafeBlockSigner(), + proposer: _doi.proposer(), + challenger: _doi.challenger() + }); + OPContractsManager.DeployInput memory deployInput = OPContractsManager.DeployInput({ + roles: roles, + basefeeScalar: _doi.basefeeScalar(), + blobBasefeeScalar: _doi.blobBaseFeeScalar(), + l2ChainId: _doi.l2ChainId(), + startingAnchorRoots: _doi.startingAnchorRoots(), + saltMixer: _doi.saltMixer(), + gasLimit: _doi.gasLimit(), + disputeGameType: _doi.disputeGameType(), + disputeAbsolutePrestate: _doi.disputeAbsolutePrestate(), + disputeMaxGameDepth: _doi.disputeMaxGameDepth(), + disputeSplitDepth: _doi.disputeSplitDepth(), + disputeClockExtension: _doi.disputeClockExtension(), + disputeMaxClockDuration: _doi.disputeMaxClockDuration() + }); + + vm.broadcast(msg.sender); + OPContractsManager.DeployOutput memory deployOutput = opcm.deploy(deployInput); + + vm.label(address(deployOutput.opChainProxyAdmin), "opChainProxyAdmin"); + vm.label(address(deployOutput.addressManager), "addressManager"); + vm.label(address(deployOutput.l1ERC721BridgeProxy), "l1ERC721BridgeProxy"); + vm.label(address(deployOutput.systemConfigProxy), "systemConfigProxy"); + vm.label(address(deployOutput.optimismMintableERC20FactoryProxy), "optimismMintableERC20FactoryProxy"); + vm.label(address(deployOutput.l1StandardBridgeProxy), "l1StandardBridgeProxy"); + vm.label(address(deployOutput.l1CrossDomainMessengerProxy), "l1CrossDomainMessengerProxy"); + vm.label(address(deployOutput.optimismPortalProxy), "optimismPortalProxy"); + vm.label(address(deployOutput.disputeGameFactoryProxy), "disputeGameFactoryProxy"); + vm.label(address(deployOutput.anchorStateRegistryProxy), "anchorStateRegistryProxy"); + vm.label(address(deployOutput.anchorStateRegistryImpl), "anchorStateRegistryImpl"); + // vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame"); + vm.label(address(deployOutput.permissionedDisputeGame), "permissionedDisputeGame"); + vm.label(address(deployOutput.delayedWETHPermissionedGameProxy), "delayedWETHPermissionedGameProxy"); + // TODO: Eventually switch from Permissioned to Permissionless. + // vm.label(address(deployOutput.delayedWETHPermissionlessGameProxy), "delayedWETHPermissionlessGameProxy"); + + _doo.set(_doo.opChainProxyAdmin.selector, address(deployOutput.opChainProxyAdmin)); + _doo.set(_doo.addressManager.selector, address(deployOutput.addressManager)); + _doo.set(_doo.l1ERC721BridgeProxy.selector, address(deployOutput.l1ERC721BridgeProxy)); + _doo.set(_doo.systemConfigProxy.selector, address(deployOutput.systemConfigProxy)); + _doo.set( + _doo.optimismMintableERC20FactoryProxy.selector, address(deployOutput.optimismMintableERC20FactoryProxy) + ); + _doo.set(_doo.l1StandardBridgeProxy.selector, address(deployOutput.l1StandardBridgeProxy)); + _doo.set(_doo.l1CrossDomainMessengerProxy.selector, address(deployOutput.l1CrossDomainMessengerProxy)); + _doo.set(_doo.optimismPortalProxy.selector, address(deployOutput.optimismPortalProxy)); + _doo.set(_doo.disputeGameFactoryProxy.selector, address(deployOutput.disputeGameFactoryProxy)); + _doo.set(_doo.anchorStateRegistryProxy.selector, address(deployOutput.anchorStateRegistryProxy)); + _doo.set(_doo.anchorStateRegistryImpl.selector, address(deployOutput.anchorStateRegistryImpl)); + // _doo.set(_doo.faultDisputeGame.selector, address(deployOutput.faultDisputeGame)); + _doo.set(_doo.permissionedDisputeGame.selector, address(deployOutput.permissionedDisputeGame)); + _doo.set(_doo.delayedWETHPermissionedGameProxy.selector, address(deployOutput.delayedWETHPermissionedGameProxy)); + // TODO: Eventually switch from Permissioned to Permissionless. + // _doo.set( + // _doo.delayedWETHPermissionlessGameProxy.selector, + // address(deployOutput.delayedWETHPermissionlessGameProxy) + // ); + + checkOutput(_doi, _doo); + } - function assertValidDeploy(DeployOPChainInput _doi) internal { - assertValidAnchorStateRegistryImpl(_doi); - assertValidAnchorStateRegistryProxy(_doi); - assertValidDelayedWETH(_doi); - assertValidDisputeGameFactory(_doi); - assertValidL1CrossDomainMessenger(_doi); - assertValidL1ERC721Bridge(_doi); - assertValidL1StandardBridge(_doi); - assertValidOptimismMintableERC20Factory(_doi); - assertValidOptimismPortal(_doi); - assertValidPermissionedDisputeGame(_doi); - assertValidSystemConfig(_doi); - assertValidAddressManager(_doi); - assertValidOPChainProxyAdmin(_doi); - } - - function assertValidPermissionedDisputeGame(DeployOPChainInput _doi) internal { - IPermissionedDisputeGame game = permissionedDisputeGame(); + function checkOutput(DeployOPChainInput _doi, DeployOPChainOutput _doo) public { + // With 16 addresses, we'd get a stack too deep error if we tried to do this inline as a + // single call to `Solarray.addresses`. So we split it into two calls. + address[] memory addrs1 = Solarray.addresses( + address(_doo.opChainProxyAdmin()), + address(_doo.addressManager()), + address(_doo.l1ERC721BridgeProxy()), + address(_doo.systemConfigProxy()), + address(_doo.optimismMintableERC20FactoryProxy()), + address(_doo.l1StandardBridgeProxy()), + address(_doo.l1CrossDomainMessengerProxy()) + ); + address[] memory addrs2 = Solarray.addresses( + address(_doo.optimismPortalProxy()), + address(_doo.disputeGameFactoryProxy()), + address(_doo.anchorStateRegistryProxy()), + address(_doo.anchorStateRegistryImpl()), + address(_doo.permissionedDisputeGame()), + // address(_doo.faultDisputeGame()), + address(_doo.delayedWETHPermissionedGameProxy()) + ); + // TODO: Eventually switch from Permissioned to Permissionless. Add this address back in. + // address(_delayedWETHPermissionlessGameProxy) + + DeployUtils.assertValidContractAddresses(Solarray.extend(addrs1, addrs2)); + assertValidDeploy(_doi, _doo); + } + + // -------- Deployment Assertions -------- + function assertValidDeploy(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + assertValidAnchorStateRegistryImpl(_doi, _doo); + assertValidAnchorStateRegistryProxy(_doi, _doo); + assertValidDelayedWETH(_doi, _doo); + assertValidDisputeGameFactory(_doi, _doo); + assertValidL1CrossDomainMessenger(_doi, _doo); + assertValidL1ERC721Bridge(_doi, _doo); + assertValidL1StandardBridge(_doi, _doo); + assertValidOptimismMintableERC20Factory(_doi, _doo); + assertValidOptimismPortal(_doi, _doo); + assertValidPermissionedDisputeGame(_doi, _doo); + assertValidSystemConfig(_doi, _doo); + assertValidAddressManager(_doi, _doo); + assertValidOPChainProxyAdmin(_doi, _doo); + } + + function assertValidPermissionedDisputeGame(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IPermissionedDisputeGame game = _doo.permissionedDisputeGame(); require(GameType.unwrap(game.gameType()) == GameType.unwrap(GameTypes.PERMISSIONED_CANNON), "DPG-10"); + + if (_doi.allowCustomDisputeParameters()) { + return; + } + // This hex string is the absolutePrestate of the latest op-program release, see where the // `EXPECTED_PRESTATE_HASH` is defined in `config.yml`. require( @@ -391,12 +479,12 @@ contract DeployOPChainOutput is BaseDeployIO { "DPG-20" ); - OPContractsManager opcm = _doi.opcmProxy(); - (address mips,) = opcm.implementations(opcm.latestRelease(), "MIPS"); - require(game.vm() == IBigStepper(mips), "DPG-30"); + OPContractsManager opcm = _doi.opcm(); + address mipsImpl = opcm.implementations().mipsImpl; + require(game.vm() == IBigStepper(mipsImpl), "DPG-30"); - require(address(game.weth()) == address(delayedWETHPermissionedGameProxy()), "DPG-40"); - require(address(game.anchorStateRegistry()) == address(anchorStateRegistryProxy()), "DPG-50"); + require(address(game.weth()) == address(_doo.delayedWETHPermissionedGameProxy()), "DPG-40"); + require(address(game.anchorStateRegistry()) == address(_doo.anchorStateRegistryProxy()), "DPG-50"); require(game.l2ChainId() == _doi.l2ChainId(), "DPG-60"); require(game.l2BlockNumber() == 0, "DPG-70"); require(Duration.unwrap(game.clockExtension()) == 10800, "DPG-80"); @@ -405,38 +493,43 @@ contract DeployOPChainOutput is BaseDeployIO { require(game.maxGameDepth() == 73, "DPG-100"); } - function assertValidAnchorStateRegistryProxy(DeployOPChainInput) internal { + function assertValidAnchorStateRegistryProxy(DeployOPChainInput, DeployOPChainOutput _doo) internal { // First we check the proxy as itself. - IProxy proxy = IProxy(payable(address(anchorStateRegistryProxy()))); + IProxy proxy = IProxy(payable(address(_doo.anchorStateRegistryProxy()))); vm.prank(address(0)); address admin = proxy.admin(); - require(admin == address(opChainProxyAdmin()), "ANCHORP-10"); + require(admin == address(_doo.opChainProxyAdmin()), "ANCHORP-10"); // Then we check the proxy as ASR. - DeployUtils.assertInitialized({ _contractAddress: address(anchorStateRegistryProxy()), _slot: 0, _offset: 0 }); + DeployUtils.assertInitialized({ + _contractAddress: address(_doo.anchorStateRegistryProxy()), + _slot: 0, + _offset: 0 + }); vm.prank(address(0)); address impl = proxy.implementation(); - require(impl == address(anchorStateRegistryImpl()), "ANCHORP-20"); + require(impl == address(_doo.anchorStateRegistryImpl()), "ANCHORP-20"); require( - address(anchorStateRegistryProxy().disputeGameFactory()) == address(disputeGameFactoryProxy()), "ANCHORP-30" + address(_doo.anchorStateRegistryProxy().disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), + "ANCHORP-30" ); - (Hash actualRoot,) = anchorStateRegistryProxy().anchors(GameTypes.PERMISSIONED_CANNON); + (Hash actualRoot,) = _doo.anchorStateRegistryProxy().anchors(GameTypes.PERMISSIONED_CANNON); bytes32 expectedRoot = 0xdead000000000000000000000000000000000000000000000000000000000000; require(Hash.unwrap(actualRoot) == expectedRoot, "ANCHORP-40"); } - function assertValidAnchorStateRegistryImpl(DeployOPChainInput) internal { - IAnchorStateRegistry registry = anchorStateRegistryImpl(); + function assertValidAnchorStateRegistryImpl(DeployOPChainInput, DeployOPChainOutput _doo) internal { + IAnchorStateRegistry registry = _doo.anchorStateRegistryImpl(); DeployUtils.assertInitialized({ _contractAddress: address(registry), _slot: 0, _offset: 0 }); - require(address(registry.disputeGameFactory()) == address(disputeGameFactoryProxy()), "ANCHORI-10"); + require(address(registry.disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), "ANCHORI-10"); } - function assertValidSystemConfig(DeployOPChainInput _doi) internal { - ISystemConfig systemConfig = systemConfigProxy(); + function assertValidSystemConfig(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + ISystemConfig systemConfig = _doo.systemConfigProxy(); DeployUtils.assertInitialized({ _contractAddress: address(systemConfig), _slot: 0, _offset: 0 }); @@ -444,7 +537,7 @@ contract DeployOPChainOutput is BaseDeployIO { require(systemConfig.basefeeScalar() == _doi.basefeeScalar(), "SYSCON-20"); require(systemConfig.blobbasefeeScalar() == _doi.blobBaseFeeScalar(), "SYSCON-30"); require(systemConfig.batcherHash() == bytes32(uint256(uint160(_doi.batcher()))), "SYSCON-40"); - require(systemConfig.gasLimit() == uint64(30_000_000), "SYSCON-50"); + require(systemConfig.gasLimit() == uint64(60_000_000), "SYSCON-50"); require(systemConfig.unsafeBlockSigner() == _doi.unsafeBlockSigner(), "SYSCON-60"); require(systemConfig.scalar() >> 248 == 1, "SYSCON-70"); @@ -458,41 +551,40 @@ contract DeployOPChainOutput is BaseDeployIO { require(outputConfig.maximumBaseFee == rConfig.maximumBaseFee, "SYSCON-130"); require(systemConfig.startBlock() == block.number, "SYSCON-140"); - require( - systemConfig.batchInbox() == _doi.opcmProxy().chainIdToBatchInboxAddress(_doi.l2ChainId()), "SYSCON-150" - ); + require(systemConfig.batchInbox() == _doi.opcm().chainIdToBatchInboxAddress(_doi.l2ChainId()), "SYSCON-150"); - require(systemConfig.l1CrossDomainMessenger() == address(l1CrossDomainMessengerProxy()), "SYSCON-160"); - require(systemConfig.l1ERC721Bridge() == address(l1ERC721BridgeProxy()), "SYSCON-170"); - require(systemConfig.l1StandardBridge() == address(l1StandardBridgeProxy()), "SYSCON-180"); - require(systemConfig.disputeGameFactory() == address(disputeGameFactoryProxy()), "SYSCON-190"); - require(systemConfig.optimismPortal() == address(optimismPortalProxy()), "SYSCON-200"); + require(systemConfig.l1CrossDomainMessenger() == address(_doo.l1CrossDomainMessengerProxy()), "SYSCON-160"); + require(systemConfig.l1ERC721Bridge() == address(_doo.l1ERC721BridgeProxy()), "SYSCON-170"); + require(systemConfig.l1StandardBridge() == address(_doo.l1StandardBridgeProxy()), "SYSCON-180"); + require(systemConfig.disputeGameFactory() == address(_doo.disputeGameFactoryProxy()), "SYSCON-190"); + require(systemConfig.optimismPortal() == address(_doo.optimismPortalProxy()), "SYSCON-200"); require( - systemConfig.optimismMintableERC20Factory() == address(optimismMintableERC20FactoryProxy()), "SYSCON-210" + systemConfig.optimismMintableERC20Factory() == address(_doo.optimismMintableERC20FactoryProxy()), + "SYSCON-210" ); (address gasPayingToken,) = systemConfig.gasPayingToken(); require(gasPayingToken == Constants.ETHER, "SYSCON-220"); } - function assertValidL1CrossDomainMessenger(DeployOPChainInput _doi) internal { - IL1CrossDomainMessenger messenger = l1CrossDomainMessengerProxy(); + function assertValidL1CrossDomainMessenger(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IL1CrossDomainMessenger messenger = _doo.l1CrossDomainMessengerProxy(); DeployUtils.assertInitialized({ _contractAddress: address(messenger), _slot: 0, _offset: 20 }); require(address(messenger.OTHER_MESSENGER()) == Predeploys.L2_CROSS_DOMAIN_MESSENGER, "L1xDM-10"); require(address(messenger.otherMessenger()) == Predeploys.L2_CROSS_DOMAIN_MESSENGER, "L1xDM-20"); - require(address(messenger.PORTAL()) == address(optimismPortalProxy()), "L1xDM-30"); - require(address(messenger.portal()) == address(optimismPortalProxy()), "L1xDM-40"); - require(address(messenger.superchainConfig()) == address(_doi.opcmProxy().superchainConfig()), "L1xDM-50"); + require(address(messenger.PORTAL()) == address(_doo.optimismPortalProxy()), "L1xDM-30"); + require(address(messenger.portal()) == address(_doo.optimismPortalProxy()), "L1xDM-40"); + require(address(messenger.superchainConfig()) == address(_doi.opcm().superchainConfig()), "L1xDM-50"); bytes32 xdmSenderSlot = vm.load(address(messenger), bytes32(uint256(204))); require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER, "L1xDM-60"); } - function assertValidL1StandardBridge(DeployOPChainInput _doi) internal { - IL1StandardBridge bridge = l1StandardBridgeProxy(); - IL1CrossDomainMessenger messenger = l1CrossDomainMessengerProxy(); + function assertValidL1StandardBridge(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IL1StandardBridge bridge = _doo.l1StandardBridgeProxy(); + IL1CrossDomainMessenger messenger = _doo.l1CrossDomainMessengerProxy(); DeployUtils.assertInitialized({ _contractAddress: address(bridge), _slot: 0, _offset: 0 }); @@ -500,37 +592,37 @@ contract DeployOPChainOutput is BaseDeployIO { require(address(bridge.messenger()) == address(messenger), "L1SB-20"); require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE, "L1SB-30"); require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE, "L1SB-40"); - require(address(bridge.superchainConfig()) == address(_doi.opcmProxy().superchainConfig()), "L1SB-50"); + require(address(bridge.superchainConfig()) == address(_doi.opcm().superchainConfig()), "L1SB-50"); } - function assertValidOptimismMintableERC20Factory(DeployOPChainInput) internal { - IOptimismMintableERC20Factory factory = optimismMintableERC20FactoryProxy(); + function assertValidOptimismMintableERC20Factory(DeployOPChainInput, DeployOPChainOutput _doo) internal { + IOptimismMintableERC20Factory factory = _doo.optimismMintableERC20FactoryProxy(); DeployUtils.assertInitialized({ _contractAddress: address(factory), _slot: 0, _offset: 0 }); - require(factory.BRIDGE() == address(l1StandardBridgeProxy()), "MERC20F-10"); - require(factory.bridge() == address(l1StandardBridgeProxy()), "MERC20F-20"); + require(factory.BRIDGE() == address(_doo.l1StandardBridgeProxy()), "MERC20F-10"); + require(factory.bridge() == address(_doo.l1StandardBridgeProxy()), "MERC20F-20"); } - function assertValidL1ERC721Bridge(DeployOPChainInput _doi) internal { - IL1ERC721Bridge bridge = l1ERC721BridgeProxy(); + function assertValidL1ERC721Bridge(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IL1ERC721Bridge bridge = _doo.l1ERC721BridgeProxy(); DeployUtils.assertInitialized({ _contractAddress: address(bridge), _slot: 0, _offset: 0 }); require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_ERC721_BRIDGE, "L721B-10"); require(address(bridge.otherBridge()) == Predeploys.L2_ERC721_BRIDGE, "L721B-20"); - require(address(bridge.MESSENGER()) == address(l1CrossDomainMessengerProxy()), "L721B-30"); - require(address(bridge.messenger()) == address(l1CrossDomainMessengerProxy()), "L721B-40"); - require(address(bridge.superchainConfig()) == address(_doi.opcmProxy().superchainConfig()), "L721B-50"); + require(address(bridge.MESSENGER()) == address(_doo.l1CrossDomainMessengerProxy()), "L721B-30"); + require(address(bridge.messenger()) == address(_doo.l1CrossDomainMessengerProxy()), "L721B-40"); + require(address(bridge.superchainConfig()) == address(_doi.opcm().superchainConfig()), "L721B-50"); } - function assertValidOptimismPortal(DeployOPChainInput _doi) internal { - IOptimismPortal2 portal = optimismPortalProxy(); - ISuperchainConfig superchainConfig = ISuperchainConfig(address(_doi.opcmProxy().superchainConfig())); + function assertValidOptimismPortal(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IOptimismPortal2 portal = _doo.optimismPortalProxy(); + ISuperchainConfig superchainConfig = ISuperchainConfig(address(_doi.opcm().superchainConfig())); - require(address(portal.disputeGameFactory()) == address(disputeGameFactoryProxy()), "PORTAL-10"); - require(address(portal.systemConfig()) == address(systemConfigProxy()), "PORTAL-20"); + require(address(portal.disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), "PORTAL-10"); + require(address(portal.systemConfig()) == address(_doo.systemConfigProxy()), "PORTAL-20"); require(address(portal.superchainConfig()) == address(superchainConfig), "PORTAL-30"); require(portal.guardian() == superchainConfig.guardian(), "PORTAL-40"); require(portal.paused() == superchainConfig.paused(), "PORTAL-50"); @@ -541,158 +633,85 @@ contract DeployOPChainOutput is BaseDeployIO { require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0)); } - function assertValidDisputeGameFactory(DeployOPChainInput _doi) internal { - IDisputeGameFactory factory = disputeGameFactoryProxy(); + function assertValidDisputeGameFactory(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IDisputeGameFactory factory = _doo.disputeGameFactoryProxy(); DeployUtils.assertInitialized({ _contractAddress: address(factory), _slot: 0, _offset: 0 }); require( - address(factory.gameImpls(GameTypes.PERMISSIONED_CANNON)) == address(permissionedDisputeGame()), "DF-10" + address(factory.gameImpls(GameTypes.PERMISSIONED_CANNON)) == address(_doo.permissionedDisputeGame()), + "DF-10" ); require(factory.owner() == address(_doi.opChainProxyAdminOwner()), "DF-20"); } - function assertValidDelayedWETH(DeployOPChainInput _doi) internal { - IDelayedWETH permissioned = delayedWETHPermissionedGameProxy(); + function assertValidDelayedWETH(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IDelayedWETH permissioned = _doo.delayedWETHPermissionedGameProxy(); require(permissioned.owner() == address(_doi.opChainProxyAdminOwner()), "DWETH-10"); IProxy proxy = IProxy(payable(address(permissioned))); vm.prank(address(0)); address admin = proxy.admin(); - require(admin == address(opChainProxyAdmin()), "DWETH-20"); + require(admin == address(_doo.opChainProxyAdmin()), "DWETH-20"); } - function assertValidAddressManager(DeployOPChainInput) internal view { - require(addressManager().owner() == address(opChainProxyAdmin()), "AM-10"); + function assertValidAddressManager(DeployOPChainInput, DeployOPChainOutput _doo) internal view { + require(_doo.addressManager().owner() == address(_doo.opChainProxyAdmin()), "AM-10"); } - function assertValidOPChainProxyAdmin(DeployOPChainInput _doi) internal { - IProxyAdmin admin = opChainProxyAdmin(); + function assertValidOPChainProxyAdmin(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IProxyAdmin admin = _doo.opChainProxyAdmin(); require(admin.owner() == _doi.opChainProxyAdminOwner(), "OPCPA-10"); require( - admin.getProxyImplementation(address(l1CrossDomainMessengerProxy())) - == DeployUtils.assertResolvedDelegateProxyImplementationSet("OVM_L1CrossDomainMessenger", addressManager()), + admin.getProxyImplementation(address(_doo.l1CrossDomainMessengerProxy())) + == DeployUtils.assertResolvedDelegateProxyImplementationSet( + "OVM_L1CrossDomainMessenger", _doo.addressManager() + ), "OPCPA-20" ); - require(address(admin.addressManager()) == address(addressManager()), "OPCPA-30"); + require(address(admin.addressManager()) == address(_doo.addressManager()), "OPCPA-30"); require( - admin.getProxyImplementation(address(l1StandardBridgeProxy())) - == DeployUtils.assertL1ChugSplashImplementationSet(address(l1StandardBridgeProxy())), + admin.getProxyImplementation(address(_doo.l1StandardBridgeProxy())) + == DeployUtils.assertL1ChugSplashImplementationSet(address(_doo.l1StandardBridgeProxy())), "OPCPA-40" ); require( - admin.getProxyImplementation(address(l1ERC721BridgeProxy())) - == DeployUtils.assertERC1967ImplementationSet(address(l1ERC721BridgeProxy())), + admin.getProxyImplementation(address(_doo.l1ERC721BridgeProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.l1ERC721BridgeProxy())), "OPCPA-50" ); require( - admin.getProxyImplementation(address(optimismPortalProxy())) - == DeployUtils.assertERC1967ImplementationSet(address(optimismPortalProxy())), + admin.getProxyImplementation(address(_doo.optimismPortalProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.optimismPortalProxy())), "OPCPA-60" ); require( - admin.getProxyImplementation(address(systemConfigProxy())) - == DeployUtils.assertERC1967ImplementationSet(address(systemConfigProxy())), + admin.getProxyImplementation(address(_doo.systemConfigProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.systemConfigProxy())), "OPCPA-70" ); require( - admin.getProxyImplementation(address(optimismMintableERC20FactoryProxy())) - == DeployUtils.assertERC1967ImplementationSet(address(optimismMintableERC20FactoryProxy())), + admin.getProxyImplementation(address(_doo.optimismMintableERC20FactoryProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.optimismMintableERC20FactoryProxy())), "OPCPA-80" ); require( - admin.getProxyImplementation(address(disputeGameFactoryProxy())) - == DeployUtils.assertERC1967ImplementationSet(address(disputeGameFactoryProxy())), + admin.getProxyImplementation(address(_doo.disputeGameFactoryProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.disputeGameFactoryProxy())), "OPCPA-90" ); require( - admin.getProxyImplementation(address(delayedWETHPermissionedGameProxy())) - == DeployUtils.assertERC1967ImplementationSet(address(delayedWETHPermissionedGameProxy())), + admin.getProxyImplementation(address(_doo.delayedWETHPermissionedGameProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.delayedWETHPermissionedGameProxy())), "OPCPA-100" ); require( - admin.getProxyImplementation(address(anchorStateRegistryProxy())) - == DeployUtils.assertERC1967ImplementationSet(address(anchorStateRegistryProxy())), + admin.getProxyImplementation(address(_doo.anchorStateRegistryProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.anchorStateRegistryProxy())), "OPCPA-110" ); } -} - -contract DeployOPChain is Script { - // -------- Core Deployment Methods -------- - - function run(DeployOPChainInput _doi, DeployOPChainOutput _doo) public { - OPContractsManager opcmProxy = _doi.opcmProxy(); - - OPContractsManager.Roles memory roles = OPContractsManager.Roles({ - opChainProxyAdminOwner: _doi.opChainProxyAdminOwner(), - systemConfigOwner: _doi.systemConfigOwner(), - batcher: _doi.batcher(), - unsafeBlockSigner: _doi.unsafeBlockSigner(), - proposer: _doi.proposer(), - challenger: _doi.challenger() - }); - OPContractsManager.DeployInput memory deployInput = OPContractsManager.DeployInput({ - roles: roles, - basefeeScalar: _doi.basefeeScalar(), - blobBasefeeScalar: _doi.blobBaseFeeScalar(), - l2ChainId: _doi.l2ChainId(), - startingAnchorRoots: _doi.startingAnchorRoots(), - saltMixer: _doi.saltMixer(), - gasLimit: _doi.gasLimit(), - disputeGameType: _doi.disputeGameType(), - disputeAbsolutePrestate: _doi.disputeAbsolutePrestate(), - disputeMaxGameDepth: _doi.disputeMaxGameDepth(), - disputeSplitDepth: _doi.disputeSplitDepth(), - disputeClockExtension: _doi.disputeClockExtension(), - disputeMaxClockDuration: _doi.disputeMaxClockDuration() - }); - - vm.broadcast(msg.sender); - OPContractsManager.DeployOutput memory deployOutput = opcmProxy.deploy(deployInput); - - vm.label(address(deployOutput.opChainProxyAdmin), "opChainProxyAdmin"); - vm.label(address(deployOutput.addressManager), "addressManager"); - vm.label(address(deployOutput.l1ERC721BridgeProxy), "l1ERC721BridgeProxy"); - vm.label(address(deployOutput.systemConfigProxy), "systemConfigProxy"); - vm.label(address(deployOutput.optimismMintableERC20FactoryProxy), "optimismMintableERC20FactoryProxy"); - vm.label(address(deployOutput.l1StandardBridgeProxy), "l1StandardBridgeProxy"); - vm.label(address(deployOutput.l1CrossDomainMessengerProxy), "l1CrossDomainMessengerProxy"); - vm.label(address(deployOutput.optimismPortalProxy), "optimismPortalProxy"); - vm.label(address(deployOutput.disputeGameFactoryProxy), "disputeGameFactoryProxy"); - vm.label(address(deployOutput.anchorStateRegistryProxy), "anchorStateRegistryProxy"); - vm.label(address(deployOutput.anchorStateRegistryImpl), "anchorStateRegistryImpl"); - // vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame"); - vm.label(address(deployOutput.permissionedDisputeGame), "permissionedDisputeGame"); - vm.label(address(deployOutput.delayedWETHPermissionedGameProxy), "delayedWETHPermissionedGameProxy"); - // TODO: Eventually switch from Permissioned to Permissionless. - // vm.label(address(deployOutput.delayedWETHPermissionlessGameProxy), "delayedWETHPermissionlessGameProxy"); - - _doo.set(_doo.opChainProxyAdmin.selector, address(deployOutput.opChainProxyAdmin)); - _doo.set(_doo.addressManager.selector, address(deployOutput.addressManager)); - _doo.set(_doo.l1ERC721BridgeProxy.selector, address(deployOutput.l1ERC721BridgeProxy)); - _doo.set(_doo.systemConfigProxy.selector, address(deployOutput.systemConfigProxy)); - _doo.set( - _doo.optimismMintableERC20FactoryProxy.selector, address(deployOutput.optimismMintableERC20FactoryProxy) - ); - _doo.set(_doo.l1StandardBridgeProxy.selector, address(deployOutput.l1StandardBridgeProxy)); - _doo.set(_doo.l1CrossDomainMessengerProxy.selector, address(deployOutput.l1CrossDomainMessengerProxy)); - _doo.set(_doo.optimismPortalProxy.selector, address(deployOutput.optimismPortalProxy)); - _doo.set(_doo.disputeGameFactoryProxy.selector, address(deployOutput.disputeGameFactoryProxy)); - _doo.set(_doo.anchorStateRegistryProxy.selector, address(deployOutput.anchorStateRegistryProxy)); - _doo.set(_doo.anchorStateRegistryImpl.selector, address(deployOutput.anchorStateRegistryImpl)); - // _doo.set(_doo.faultDisputeGame.selector, address(deployOutput.faultDisputeGame)); - _doo.set(_doo.permissionedDisputeGame.selector, address(deployOutput.permissionedDisputeGame)); - _doo.set(_doo.delayedWETHPermissionedGameProxy.selector, address(deployOutput.delayedWETHPermissionedGameProxy)); - // TODO: Eventually switch from Permissioned to Permissionless. - // _doo.set( - // _doo.delayedWETHPermissionlessGameProxy.selector, - // address(deployOutput.delayedWETHPermissionlessGameProxy) - // ); - - _doo.checkOutput(_doi); - } // -------- Utilities -------- diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol index e126c39f18c9f..29709f2b125fc 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol @@ -16,7 +16,7 @@ import { Deployer } from "scripts/deploy/Deployer.sol"; import { LivenessGuard } from "src/safe/LivenessGuard.sol"; import { LivenessModule } from "src/safe/LivenessModule.sol"; import { DeputyGuardianModule } from "src/safe/DeputyGuardianModule.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { Deploy } from "./Deploy.s.sol"; @@ -59,7 +59,7 @@ struct GuardianConfig { /// be used as an example to guide the setup and configuration of the Safe contracts. contract DeployOwnership is Deploy { /// @notice Internal function containing the deploy logic. - function _run() internal override { + function _run(bool) internal override { console.log("start of Ownership Deployment"); // The SuperchainConfig is needed as a constructor argument to the Deputy Guardian Module deploySuperchainConfig(); diff --git a/packages/contracts-bedrock/scripts/DeploySuperchain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol similarity index 98% rename from packages/contracts-bedrock/scripts/DeploySuperchain.s.sol rename to packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol index 38c87f6234434..f039fa47ef99c 100644 --- a/packages/contracts-bedrock/scripts/DeploySuperchain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol @@ -4,14 +4,14 @@ pragma solidity 0.8.15; import { Script } from "forge-std/Script.sol"; import { stdToml } from "forge-std/StdToml.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; -import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; -import { BaseDeployIO } from "scripts/utils/BaseDeployIO.sol"; +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; // This comment block defines the requirements and rationale for the architecture used in this forge // script, along with other scripts that are being written as new Superchain-first deploy scripts to @@ -60,9 +60,9 @@ import { BaseDeployIO } from "scripts/utils/BaseDeployIO.sol"; // we use variable names that are shorthand for the full contract names, for example: // - `dsi` for DeploySuperchainInput // - `dso` for DeploySuperchainOutput -// - `dio` for DeployImplementationsInput +// - `dii` for DeployImplementationsInput // - `dio` for DeployImplementationsOutput -// - `doo` for DeployOPChainInput +// - `doi` for DeployOPChainInput // - `doo` for DeployOPChainOutput // - etc. diff --git a/packages/contracts-bedrock/scripts/deploy/Deployer.sol b/packages/contracts-bedrock/scripts/deploy/Deployer.sol index 36de8dd440fc1..fa7454f5ee1dc 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deployer.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deployer.sol @@ -5,8 +5,8 @@ import { Script } from "forge-std/Script.sol"; import { Artifacts } from "scripts/Artifacts.s.sol"; import { Config } from "scripts/libraries/Config.sol"; import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; -import { Executables } from "scripts/libraries/Executables.sol"; import { console } from "forge-std/console.sol"; +import { Process } from "scripts/libraries/Process.sol"; /// @title Deployer /// @author tynes @@ -19,11 +19,20 @@ abstract contract Deployer is Script, Artifacts { function setUp() public virtual override { Artifacts.setUp(); - console.log("Commit hash: %s", Executables.gitCommitHash()); + console.log("Commit hash: %s", gitCommitHash()); vm.etch(address(cfg), vm.getDeployedCode("DeployConfig.s.sol:DeployConfig")); vm.label(address(cfg), "DeployConfig"); vm.allowCheatcodes(address(cfg)); cfg.read(Config.deployConfigPath()); } + + /// @notice Returns the commit hash of HEAD. If no git repository is + /// found, it will return the contents of the .gitcommit file. Otherwise, + /// it will return an error. The .gitcommit file is used to store the + /// git commit of the contracts when they are packaged into docker images + /// in order to avoid the need to have a git repository in the image. + function gitCommitHash() internal returns (string memory) { + return Process.bash("cast abi-encode 'f(string)' $(git rev-parse HEAD || cat .gitcommit)"); + } } diff --git a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol new file mode 100644 index 0000000000000..8917479543e54 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { Script } from "forge-std/Script.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; +import { DeployOPChainOutput } from "scripts/deploy/DeployOPChain.s.sol"; +import { IMIPS } from "interfaces/cannon/IMIPS.sol"; +import { OPContractsManager } from "src/L1/OPContractsManager.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IStaticL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; + +contract ReadImplementationAddressesInput is DeployOPChainOutput { + OPContractsManager internal _opcm; + + function set(bytes4 _sel, address _addr) public override { + require(_addr != address(0), "ReadImplementationAddressesInput: cannot set zero address"); + if (_sel == this.opcm.selector) _opcm = OPContractsManager(_addr); + else if (_sel == this.addressManager.selector) _addressManager = IAddressManager(_addr); + else super.set(_sel, _addr); + } + + function opcm() public view returns (OPContractsManager) { + DeployUtils.assertValidContractAddress(address(_opcm)); + return _opcm; + } +} + +contract ReadImplementationAddressesOutput is BaseDeployIO { + address internal _delayedWETH; + address internal _optimismPortal; + address internal _systemConfig; + address internal _l1CrossDomainMessenger; + address internal _l1ERC721Bridge; + address internal _l1StandardBridge; + address internal _optimismMintableERC20Factory; + address internal _disputeGameFactory; + address internal _mipsSingleton; + address internal _preimageOracleSingleton; + + function set(bytes4 _sel, address _addr) public { + require(_addr != address(0), "ReadImplementationAddressesOutput: cannot set zero address"); + if (_sel == this.delayedWETH.selector) _delayedWETH = _addr; + else if (_sel == this.optimismPortal.selector) _optimismPortal = _addr; + else if (_sel == this.systemConfig.selector) _systemConfig = _addr; + else if (_sel == this.l1CrossDomainMessenger.selector) _l1CrossDomainMessenger = _addr; + else if (_sel == this.l1ERC721Bridge.selector) _l1ERC721Bridge = _addr; + else if (_sel == this.l1StandardBridge.selector) _l1StandardBridge = _addr; + else if (_sel == this.optimismMintableERC20Factory.selector) _optimismMintableERC20Factory = _addr; + else if (_sel == this.disputeGameFactory.selector) _disputeGameFactory = _addr; + else if (_sel == this.mipsSingleton.selector) _mipsSingleton = _addr; + else if (_sel == this.preimageOracleSingleton.selector) _preimageOracleSingleton = _addr; + else revert("ReadImplementationAddressesOutput: unknown selector"); + } + + function delayedWETH() public view returns (address) { + require(_delayedWETH != address(0), "ReadImplementationAddressesOutput: delayedWETH not set"); + return _delayedWETH; + } + + function optimismPortal() public view returns (address) { + require(_optimismPortal != address(0), "ReadImplementationAddressesOutput: optimismPortal not set"); + return _optimismPortal; + } + + function systemConfig() public view returns (address) { + require(_systemConfig != address(0), "ReadImplementationAddressesOutput: systemConfig not set"); + return _systemConfig; + } + + function l1CrossDomainMessenger() public view returns (address) { + require( + _l1CrossDomainMessenger != address(0), "ReadImplementationAddressesOutput: l1CrossDomainMessenger not set" + ); + return _l1CrossDomainMessenger; + } + + function l1ERC721Bridge() public view returns (address) { + require(_l1ERC721Bridge != address(0), "ReadImplementationAddressesOutput: l1ERC721Bridge not set"); + return _l1ERC721Bridge; + } + + function l1StandardBridge() public view returns (address) { + require(_l1StandardBridge != address(0), "ReadImplementationAddressesOutput: l1StandardBridge not set"); + return _l1StandardBridge; + } + + function optimismMintableERC20Factory() public view returns (address) { + require( + _optimismMintableERC20Factory != address(0), + "ReadImplementationAddressesOutput: optimismMintableERC20Factory not set" + ); + return _optimismMintableERC20Factory; + } + + function disputeGameFactory() public view returns (address) { + require(_disputeGameFactory != address(0), "ReadImplementationAddressesOutput: disputeGameFactory not set"); + return _disputeGameFactory; + } + + function mipsSingleton() public view returns (address) { + require(_mipsSingleton != address(0), "ReadImplementationAddressesOutput: mipsSingleton not set"); + return _mipsSingleton; + } + + function preimageOracleSingleton() public view returns (address) { + require( + _preimageOracleSingleton != address(0), "ReadImplementationAddressesOutput: preimageOracleSingleton not set" + ); + return _preimageOracleSingleton; + } +} + +contract ReadImplementationAddresses is Script { + function run(ReadImplementationAddressesInput _rii, ReadImplementationAddressesOutput _rio) public { + address[6] memory eip1967Proxies = [ + address(_rii.delayedWETHPermissionedGameProxy()), + address(_rii.optimismPortalProxy()), + address(_rii.systemConfigProxy()), + address(_rii.l1ERC721BridgeProxy()), + address(_rii.optimismMintableERC20FactoryProxy()), + address(_rii.disputeGameFactoryProxy()) + ]; + + bytes4[6] memory sels = [ + _rio.delayedWETH.selector, + _rio.optimismPortal.selector, + _rio.systemConfig.selector, + _rio.l1ERC721Bridge.selector, + _rio.optimismMintableERC20Factory.selector, + _rio.disputeGameFactory.selector + ]; + + for (uint256 i = 0; i < eip1967Proxies.length; i++) { + IProxy proxy = IProxy(payable(eip1967Proxies[i])); + vm.prank(address(0)); + _rio.set(sels[i], proxy.implementation()); + } + + vm.prank(address(0)); + address l1SBImpl = IStaticL1ChugSplashProxy(address(_rii.l1StandardBridgeProxy())).getImplementation(); + vm.prank(address(0)); + _rio.set(_rio.l1StandardBridge.selector, l1SBImpl); + + address mipsLogic = _rii.opcm().implementations().mipsImpl; + _rio.set(_rio.mipsSingleton.selector, mipsLogic); + + address delayedWETH = _rii.opcm().implementations().delayedWETHImpl; + _rio.set(_rio.delayedWETH.selector, delayedWETH); + + IAddressManager am = _rii.addressManager(); + _rio.set(_rio.l1CrossDomainMessenger.selector, am.getAddress("OVM_L1CrossDomainMessenger")); + + address preimageOracle = address(IMIPS(mipsLogic).oracle()); + _rio.set(_rio.preimageOracleSingleton.selector, preimageOracle); + } +} diff --git a/packages/contracts-bedrock/scripts/getting-started/wallets.sh b/packages/contracts-bedrock/scripts/getting-started/wallets.sh index 1d3ebfc6bbd5b..36a707c431ccf 100755 --- a/packages/contracts-bedrock/scripts/getting-started/wallets.sh +++ b/packages/contracts-bedrock/scripts/getting-started/wallets.sh @@ -10,18 +10,21 @@ wallet1=$(cast wallet new) wallet2=$(cast wallet new) wallet3=$(cast wallet new) wallet4=$(cast wallet new) +wallet5=$(cast wallet new) # Grab wallet addresses address1=$(echo "$wallet1" | awk '/Address/ { print $2 }') address2=$(echo "$wallet2" | awk '/Address/ { print $2 }') address3=$(echo "$wallet3" | awk '/Address/ { print $2 }') address4=$(echo "$wallet4" | awk '/Address/ { print $2 }') +address5=$(echo "$wallet5" | awk '/Address/ { print $2 }') # Grab wallet private keys key1=$(echo "$wallet1" | awk '/Private key/ { print $3 }') key2=$(echo "$wallet2" | awk '/Private key/ { print $3 }') key3=$(echo "$wallet3" | awk '/Private key/ { print $3 }') key4=$(echo "$wallet4" | awk '/Private key/ { print $3 }') +key5=$(echo "$wallet5" | awk '/Private key/ { print $3 }') # Print out the environment variables to copy echo "# Copy the following into your .envrc file:" @@ -41,3 +44,7 @@ echo echo "# Sequencer account" echo "export GS_SEQUENCER_ADDRESS=$address4" echo "export GS_SEQUENCER_PRIVATE_KEY=$key4" +echo +echo "# Challenger account" +echo "export GS_CHALLENGER_ADDRESS=$address5" +echo "export GS_CHALLENGER_PRIVATE_KEY=$key5" diff --git a/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go b/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go index 5eef7f1389778..e35d2a82c3036 100644 --- a/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go +++ b/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -359,70 +360,73 @@ func DiffTestUtils() { // Print the output fmt.Print(hexutil.Encode(packed[32:])) case "cannonMemoryProof": - // - // Generates a memory proof of `memAddr` for a trie containing memValue and memValue2 + // + // Generates memory proofs of `memAddr0` for a trie containing memValue0 and `memAddr1` for a trie containing memValue1 and memValue2 + // For the cannon stf, this is equivalent to the prestate proofs of the program counter and memory access for instruction execution mem := memory.NewMemory() if len(args) != 3 && len(args) != 5 && len(args) != 7 { panic("Error: cannonMemoryProofWithProof requires 2, 4, or 6 arguments") } - pc, err := strconv.ParseUint(args[1], 10, 32) + memAddr0, err := strconv.ParseUint(args[1], 10, arch.WordSize) checkErr(err, "Error decoding addr") - insn, err := strconv.ParseUint(args[2], 10, 32) - checkErr(err, "Error decoding insn") - mem.SetUint32(uint32(pc), uint32(insn)) + memValue0, err := strconv.ParseUint(args[2], 10, arch.WordSize) + checkErr(err, "Error decoding memValue0") + mem.SetWord(arch.Word(memAddr0), arch.Word(memValue0)) - var insnProof, memProof [896]byte + var proof1 []byte if len(args) >= 5 { - memAddr, err := strconv.ParseUint(args[3], 10, 32) + memAddr, err := strconv.ParseUint(args[3], 10, arch.WordSize) checkErr(err, "Error decoding memAddr") - memValue, err := strconv.ParseUint(args[4], 10, 32) + memValue, err := strconv.ParseUint(args[4], 10, arch.WordSize) checkErr(err, "Error decoding memValue") - mem.SetUint32(uint32(memAddr), uint32(memValue)) - memProof = mem.MerkleProof(uint32(memAddr)) + mem.SetWord(arch.Word(memAddr), arch.Word(memValue)) + proof := mem.MerkleProof(arch.Word(memAddr)) + proof1 = proof[:] } if len(args) == 7 { - memAddr, err := strconv.ParseUint(args[5], 10, 32) + memAddr, err := strconv.ParseUint(args[5], 10, arch.WordSize) checkErr(err, "Error decoding memAddr") - memValue, err := strconv.ParseUint(args[6], 10, 32) + memValue, err := strconv.ParseUint(args[6], 10, arch.WordSize) checkErr(err, "Error decoding memValue") - mem.SetUint32(uint32(memAddr), uint32(memValue)) - memProof = mem.MerkleProof(uint32(memAddr)) + mem.SetWord(arch.Word(memAddr), arch.Word(memValue)) + proof := mem.MerkleProof(arch.Word(memAddr)) + proof1 = proof[:] } - insnProof = mem.MerkleProof(uint32(pc)) + proof0 := mem.MerkleProof(arch.Word(memAddr0)) output := struct { MemRoot common.Hash Proof []byte }{ MemRoot: mem.MerkleRoot(), - Proof: append(insnProof[:], memProof[:]...), + Proof: append(proof0[:], proof1...), } packed, err := cannonMemoryProofArgs.Pack(&output) checkErr(err, "Error encoding output") fmt.Print(hexutil.Encode(packed[32:])) case "cannonMemoryProof2": - // - // Generates a memory proof of memAddr2 for a trie containing memValue + // + // Generates memory proof of `memAddr2` for a trie containing `memValue0` and `memValue1` mem := memory.NewMemory() if len(args) != 6 { panic("Error: cannonMemoryProofWithProof2 requires 5 arguments") } - pc, err := strconv.ParseUint(args[1], 10, 32) + memAddr0, err := strconv.ParseUint(args[1], 10, arch.WordSize) checkErr(err, "Error decoding addr") - insn, err := strconv.ParseUint(args[2], 10, 32) - checkErr(err, "Error decoding insn") - mem.SetUint32(uint32(pc), uint32(insn)) + memValue0, err := strconv.ParseUint(args[2], 10, arch.WordSize) + checkErr(err, "Error decoding memValue0") + mem.SetWord(arch.Word(memAddr0), arch.Word(memValue0)) - var memProof [896]byte - memAddr, err := strconv.ParseUint(args[3], 10, 32) + var memProof [memory.MemProofSize]byte + memAddr, err := strconv.ParseUint(args[3], 10, arch.WordSize) checkErr(err, "Error decoding memAddr") - memValue, err := strconv.ParseUint(args[4], 10, 32) - checkErr(err, "Error decoding memValue") - mem.SetUint32(uint32(memAddr), uint32(memValue)) + memValue1, err := strconv.ParseUint(args[4], 10, arch.WordSize) + checkErr(err, "Error decoding memValue1") + mem.SetWord(arch.Word(memAddr), arch.Word(memValue1)) - memAddr2, err := strconv.ParseUint(args[5], 10, 32) + memAddr2, err := strconv.ParseUint(args[5], 10, arch.WordSize) checkErr(err, "Error decoding memAddr") - memProof = mem.MerkleProof(uint32(memAddr2)) + memProof = mem.MerkleProof(arch.Word(memAddr2)) output := struct { MemRoot common.Hash @@ -435,27 +439,27 @@ func DiffTestUtils() { checkErr(err, "Error encoding output") fmt.Print(hexutil.Encode(packed[32:])) case "cannonMemoryProofWrongLeaf": - // + // mem := memory.NewMemory() if len(args) != 5 { panic("Error: cannonMemoryProofWrongLeaf requires 4 arguments") } - pc, err := strconv.ParseUint(args[1], 10, 32) - checkErr(err, "Error decoding addr") - insn, err := strconv.ParseUint(args[2], 10, 32) - checkErr(err, "Error decoding insn") - mem.SetUint32(uint32(pc), uint32(insn)) - - var insnProof, memProof [896]byte - memAddr, err := strconv.ParseUint(args[3], 10, 32) - checkErr(err, "Error decoding memAddr") - memValue, err := strconv.ParseUint(args[4], 10, 32) - checkErr(err, "Error decoding memValue") - mem.SetUint32(uint32(memAddr), uint32(memValue)) + memAddr0, err := strconv.ParseUint(args[1], 10, arch.WordSize) + checkErr(err, "Error decoding memAddr0") + memValue0, err := strconv.ParseUint(args[2], 10, arch.WordSize) + checkErr(err, "Error decoding memValue0") + mem.SetWord(arch.Word(memAddr0), arch.Word(memValue0)) + + var insnProof, memProof [memory.MemProofSize]byte + memAddr1, err := strconv.ParseUint(args[3], 10, arch.WordSize) + checkErr(err, "Error decoding memAddr1") + memValue1, err := strconv.ParseUint(args[4], 10, arch.WordSize) + checkErr(err, "Error decoding memValue1") + mem.SetWord(arch.Word(memAddr1), arch.Word(memValue1)) // Compute a valid proof for the root, but for the wrong leaves. - memProof = mem.MerkleProof(uint32(memAddr + 32)) - insnProof = mem.MerkleProof(uint32(pc + 32)) + memProof = mem.MerkleProof(arch.Word(memAddr1 + arch.WordSize)) + insnProof = mem.MerkleProof(arch.Word(memAddr0 + arch.WordSize)) output := struct { MemRoot common.Hash diff --git a/packages/contracts-bedrock/scripts/libraries/Config.sol b/packages/contracts-bedrock/scripts/libraries/Config.sol index 9fb43219eb930..f8ead9c705d73 100644 --- a/packages/contracts-bedrock/scripts/libraries/Config.sol +++ b/packages/contracts-bedrock/scripts/libraries/Config.sol @@ -33,10 +33,11 @@ enum Fork { DELTA, ECOTONE, FJORD, - GRANITE + GRANITE, + HOLOCENE } -Fork constant LATEST_FORK = Fork.GRANITE; +Fork constant LATEST_FORK = Fork.HOLOCENE; library ForkUtils { function toString(Fork _fork) internal pure returns (string memory) { @@ -50,6 +51,8 @@ library ForkUtils { return "fjord"; } else if (_fork == Fork.GRANITE) { return "granite"; + } else if (_fork == Fork.HOLOCENE) { + return "holocene"; } else { return "unknown"; } @@ -172,6 +175,8 @@ library Config { return Fork.FJORD; } else if (forkHash == keccak256(bytes("granite"))) { return Fork.GRANITE; + } else if (forkHash == keccak256(bytes("holocene"))) { + return Fork.HOLOCENE; } else { revert(string.concat("Config: unknown fork: ", forkStr)); } diff --git a/packages/contracts-bedrock/scripts/libraries/Constants.sol b/packages/contracts-bedrock/scripts/libraries/Constants.sol index 093bb04369a99..d388fb37778ed 100644 --- a/packages/contracts-bedrock/scripts/libraries/Constants.sol +++ b/packages/contracts-bedrock/scripts/libraries/Constants.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { GameTypes, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; /// @title Constants @@ -10,12 +10,16 @@ import { GameTypes, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; /// should be defined in that contract instead. library Constants { /// @notice Returns the default starting anchor roots value to be used in a new dispute game. + function DEFAULT_OUTPUT_ROOT() internal pure returns (OutputRoot memory) { + return OutputRoot({ root: Hash.wrap(bytes32(hex"dead")), l2BlockNumber: 0 }); + } + function DEFAULT_STARTING_ANCHOR_ROOTS() internal pure returns (IAnchorStateRegistry.StartingAnchorRoot[] memory) { IAnchorStateRegistry.StartingAnchorRoot[] memory defaultStartingAnchorRoots = new IAnchorStateRegistry.StartingAnchorRoot[](1); defaultStartingAnchorRoots[0] = IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.PERMISSIONED_CANNON, - outputRoot: OutputRoot({ root: Hash.wrap(bytes32(hex"dead")), l2BlockNumber: 0 }) + outputRoot: DEFAULT_OUTPUT_ROOT() }); return defaultStartingAnchorRoots; } diff --git a/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol b/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol index 669ebd0f65c7d..44d71addb2555 100644 --- a/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol +++ b/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; // Scripts import { Vm } from "forge-std/Vm.sol"; @@ -12,10 +12,10 @@ import { Bytes } from "src/libraries/Bytes.sol"; import { Constants } from "src/libraries/Constants.sol"; // Interfaces -import { IProxy } from "src/universal/interfaces/IProxy.sol"; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IL1ChugSplashProxy, IStaticL1ChugSplashProxy } from "src/legacy/interfaces/IL1ChugSplashProxy.sol"; -import { IResolvedDelegateProxy } from "src/legacy/interfaces/IResolvedDelegateProxy.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IL1ChugSplashProxy, IStaticL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; +import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; library DeployUtils { Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -103,6 +103,11 @@ library DeployUtils { require(preComputedAddress.code.length == 0, "DeployUtils: contract already deployed"); assembly { addr_ := create2(0, add(initCode, 0x20), mload(initCode), _salt) + if iszero(addr_) { + let size := returndatasize() + returndatacopy(0, 0, size) + revert(0, size) + } } assertValidContractAddress(addr_); } @@ -215,6 +220,10 @@ library DeployUtils { /// @notice Asserts that the given address is a valid contract address. /// @param _who Address to check. function assertValidContractAddress(address _who) internal view { + // Foundry will set returned address to address(1) whenever a contract creation fails + // inside of a test. If this is the case then let Foundry handle the error itself and don't + // trigger a revert (which would probably break a test). + if (_who == address(1)) return; require(_who != address(0), "DeployUtils: zero address"); require(_who.code.length > 0, string.concat("DeployUtils: no code at ", LibString.toHexStringChecksummed(_who))); } @@ -254,7 +263,7 @@ library DeployUtils { /// @notice Builds an ERC1967 Proxy with a dummy implementation. /// @param _proxyImplName Name of the implementation contract. - function buildERC1967ProxyWithImpl(string memory _proxyImplName) public returns (IProxy genericProxy_) { + function buildERC1967ProxyWithImpl(string memory _proxyImplName) internal returns (IProxy genericProxy_) { genericProxy_ = IProxy( create1({ _name: "Proxy", @@ -270,7 +279,10 @@ library DeployUtils { /// @notice Builds an L1ChugSplashProxy with a dummy implementation. /// @param _proxyImplName Name of the implementation contract. - function buildL1ChugSplashProxyWithImpl(string memory _proxyImplName) public returns (IL1ChugSplashProxy proxy_) { + function buildL1ChugSplashProxyWithImpl(string memory _proxyImplName) + internal + returns (IL1ChugSplashProxy proxy_) + { proxy_ = IL1ChugSplashProxy( create1({ _name: "L1ChugSplashProxy", @@ -290,7 +302,7 @@ library DeployUtils { IAddressManager _addressManager, string memory _proxyImplName ) - public + internal returns (IResolvedDelegateProxy proxy_) { proxy_ = IResolvedDelegateProxy( @@ -307,7 +319,7 @@ library DeployUtils { } /// @notice Builds an AddressManager contract. - function buildAddressManager() public returns (IAddressManager addressManager_) { + function buildAddressManager() internal returns (IAddressManager addressManager_) { addressManager_ = IAddressManager( create1({ _name: "AddressManager", diff --git a/packages/contracts-bedrock/scripts/libraries/Executables.sol b/packages/contracts-bedrock/scripts/libraries/Executables.sol deleted file mode 100644 index 0dc91e32072db..0000000000000 --- a/packages/contracts-bedrock/scripts/libraries/Executables.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { Vm } from "forge-std/Vm.sol"; -import { Process } from "scripts/libraries/Process.sol"; - -/// @notice The executables used in ffi commands. These are set here -/// to have a single source of truth in case absolute paths -/// need to be used. -library Executables { - /// @notice Foundry cheatcode VM. - Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - string internal constant bash = "bash"; - string internal constant jq = "jq"; - string internal constant forge = "forge"; - string internal constant echo = "echo"; - string internal constant sed = "sed"; - string internal constant find = "find"; - string internal constant ls = "ls"; - string internal constant git = "git"; - - /// @notice Returns the commit hash of HEAD. If no git repository is - /// found, it will return the contents of the .gitcommit file. Otherwise, - /// it will return an error. The .gitcommit file is used to store the - /// git commit of the contracts when they are packaged into docker images - /// in order to avoid the need to have a git repository in the image. - function gitCommitHash() internal returns (string memory) { - string[] memory commands = new string[](3); - commands[0] = bash; - commands[1] = "-c"; - commands[2] = "cast abi-encode 'f(string)' $(git rev-parse HEAD || cat .gitcommit)"; - return abi.decode(Process.run(commands), (string)); - } -} diff --git a/packages/contracts-bedrock/scripts/libraries/ForgeArtifacts.sol b/packages/contracts-bedrock/scripts/libraries/ForgeArtifacts.sol index 944206694d78c..dba01345d05e9 100644 --- a/packages/contracts-bedrock/scripts/libraries/ForgeArtifacts.sol +++ b/packages/contracts-bedrock/scripts/libraries/ForgeArtifacts.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; import { Vm } from "forge-std/Vm.sol"; import { stdJson } from "forge-std/StdJson.sol"; import { LibString } from "@solady/utils/LibString.sol"; -import { Executables } from "scripts/libraries/Executables.sol"; import { Process } from "scripts/libraries/Process.sol"; /// @notice Contains information about a storage slot. Mirrors the layout of the storage @@ -37,14 +36,7 @@ library ForgeArtifacts { /// @notice Removes the semantic versioning from a contract name. The semver will exist if the contract is compiled /// more than once with different versions of the compiler. function _stripSemver(string memory _name) internal returns (string memory out_) { - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat( - Executables.echo, " ", _name, " | ", Executables.sed, " -E 's/[.][0-9]+\\.[0-9]+\\.[0-9]+//g'" - ); - bytes memory res = Process.run(cmd); - out_ = string(res); + out_ = Process.bash(string.concat("echo ", _name, " | sed -E 's/[.][0-9]+\\.[0-9]+\\.[0-9]+//g'")); } /// @notice Builds the fully qualified name of a contract. Assumes that the @@ -56,48 +48,33 @@ library ForgeArtifacts { /// @notice Returns the storage layout for a deployed contract. function getStorageLayout(string memory _name) internal returns (string memory layout_) { - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat(Executables.jq, " -r '.storageLayout' < ", _getForgeArtifactPath(_name)); - bytes memory res = Process.run(cmd); - layout_ = string(res); + layout_ = Process.bash(string.concat("jq -r '.storageLayout' < ", _getForgeArtifactPath(_name))); } /// @notice Returns the abi from a the forge artifact function getAbi(string memory _name) internal returns (string memory abi_) { - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat(Executables.jq, " -r '.abi' < ", _getForgeArtifactPath(_name)); - bytes memory res = Process.run(cmd); - abi_ = string(res); + abi_ = Process.bash(string.concat("jq -r '.abi' < ", _getForgeArtifactPath(_name))); } /// @notice Returns the methodIdentifiers from the forge artifact function getMethodIdentifiers(string memory _name) internal returns (string[] memory ids_) { - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat(Executables.jq, " '.methodIdentifiers // {} | keys ' < ", _getForgeArtifactPath(_name)); - bytes memory res = Process.run(cmd, true); - ids_ = stdJson.readStringArray(string(res), ""); + string memory res = Process.bash({ + _command: string.concat("jq '.methodIdentifiers // {} | keys ' < ", _getForgeArtifactPath(_name)), + _allowEmpty: true + }); + ids_ = stdJson.readStringArray(res, ""); } /// @notice Returns the kind of contract (i.e. library, contract, or interface). /// @param _name The name of the contract to get the kind of. /// @return kind_ The kind of contract ("library", "contract", or "interface"). function getContractKind(string memory _name) internal returns (string memory kind_) { - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat( - Executables.jq, - " -r '.ast.nodes[] | select(.nodeType == \"ContractDefinition\") | .contractKind' < ", - _getForgeArtifactPath(_name) + kind_ = Process.bash( + string.concat( + "jq -r '.ast.nodes[] | select(.nodeType == \"ContractDefinition\") | .contractKind' < ", + _getForgeArtifactPath(_name) + ) ); - bytes memory res = Process.run(cmd); - kind_ = string(res); } /// @notice Returns whether or not a contract is proxied. @@ -109,19 +86,14 @@ library ForgeArtifacts { // contract. We should consider determining whether a contract is proxied based on the // deployment script since it's the source of truth for that. Current deployment script // does not make this easy but an updated script should likely make this possible. - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat( - Executables.jq, - " -r '.rawMetadata' ", - _getForgeArtifactPath(_name), - " | ", - Executables.jq, - " -r '.output.devdoc' | jq -r 'has(\"custom:proxied\")'" + string memory res = Process.bash( + string.concat( + "jq -r '.rawMetadata' ", + _getForgeArtifactPath(_name), + " | jq -r '.output.devdoc' | jq -r 'has(\"custom:proxied\")'" + ) ); - bytes memory res = Process.run(cmd); - out_ = stdJson.readBool(string(res), ""); + out_ = stdJson.readBool(res, ""); } /// @notice Returns whether or not a contract is predeployed. @@ -130,27 +102,18 @@ library ForgeArtifacts { function isPredeployedContract(string memory _name) internal returns (bool out_) { // TODO: Similar to the above, using the `@custom:predeployed` tag is not reliable but // functional for now. Deployment script should make this easier to determine. - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat( - Executables.jq, - " -r '.rawMetadata' ", - _getForgeArtifactPath(_name), - " | ", - Executables.jq, - " -r '.output.devdoc' | jq -r 'has(\"custom:predeploy\")'" + string memory res = Process.bash( + string.concat( + "jq -r '.rawMetadata' ", + _getForgeArtifactPath(_name), + " | jq -r '.output.devdoc' | jq -r 'has(\"custom:predeploy\")'" + ) ); - bytes memory res = Process.run(cmd); - out_ = stdJson.readBool(string(res), ""); + out_ = stdJson.readBool(res, ""); } function _getForgeArtifactDirectory(string memory _name) internal returns (string memory dir_) { - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat(Executables.forge, " config --json | ", Executables.jq, " -r .out"); - bytes memory res = Process.run(cmd); + string memory res = Process.bash("forge config --json | jq -r .out"); string memory contractName = _stripSemver(_name); dir_ = string.concat(vm.projectRoot(), "/", string(res), "/", contractName, ".sol"); } @@ -164,19 +127,10 @@ library ForgeArtifacts { return path; } - string[] memory cmd = new string[](3); - cmd[0] = Executables.bash; - cmd[1] = "-c"; - cmd[2] = string.concat( - Executables.ls, - " -1 --color=never ", - directory, - " | ", - Executables.jq, - " -R -s -c 'split(\"\n\") | map(select(length > 0))'" + string memory res = Process.bash( + string.concat("ls -1 --color=never ", directory, " | jq -R -s -c 'split(\"\n\") | map(select(length > 0))'") ); - bytes memory res = Process.run(cmd); - string[] memory files = stdJson.readStringArray(string(res), ""); + string[] memory files = stdJson.readStringArray(res, ""); out_ = string.concat(directory, "/", files[0]); } @@ -198,23 +152,19 @@ library ForgeArtifacts { slotType = "t_bool"; } - string[] memory command = new string[](3); - command[0] = Executables.bash; - command[1] = "-c"; - command[2] = string.concat( - Executables.echo, - " '", - storageLayout, - "'", - " | ", - Executables.jq, - " '.storage[] | select(.label == \"", - slotName, - "\" and .type == \"", - slotType, - "\")'" + bytes memory rawSlot = vm.parseJson( + Process.bash( + string.concat( + "echo '", + storageLayout, + "' | jq '.storage[] | select(.label == \"", + slotName, + "\" and .type == \"", + slotType, + "\")'" + ) + ) ); - bytes memory rawSlot = vm.parseJson(string(Process.run(command))); slot_ = abi.decode(rawSlot, (StorageSlot)); } @@ -245,25 +195,19 @@ library ForgeArtifacts { } } - string[] memory command = new string[](3); - command[0] = Executables.bash; - command[1] = "-c"; - command[2] = string.concat( - Executables.find, - " ", - _path, - bytes(pathExcludesPat).length > 0 ? string.concat(" ! \\( ", pathExcludesPat, " \\)") : "", - " -type f ", - "-exec basename {} \\;", - " | ", - Executables.sed, - " 's/\\.[^.]*$//'", - " | ", - Executables.jq, - " -R -s 'split(\"\n\")[:-1]'" + contractNames_ = abi.decode( + vm.parseJson( + Process.bash( + string.concat( + "find ", + _path, + bytes(pathExcludesPat).length > 0 ? string.concat(" ! \\( ", pathExcludesPat, " \\)") : "", + " -type f -exec basename {} \\; | sed 's/\\.[^.]*$//' | jq -R -s 'split(\"\n\")[:-1]'" + ) + ) + ), + (string[]) ); - - contractNames_ = abi.decode(vm.parseJson(string(Process.run(command))), (string[])); } /// @notice Returns the function ABIs of all L1 contracts. diff --git a/packages/contracts-bedrock/scripts/libraries/Process.sol b/packages/contracts-bedrock/scripts/libraries/Process.sol index d2cf5c3af4aa0..7a8c939f11f83 100644 --- a/packages/contracts-bedrock/scripts/libraries/Process.sol +++ b/packages/contracts-bedrock/scripts/libraries/Process.sol @@ -10,6 +10,25 @@ library Process { /// @notice Foundry cheatcode VM. Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + /// @notice Executes a bash command in a subprocess and returns its output as a string. Will revert if the command + /// returns no output. + /// @param _command The bash command to execute + function bash(string memory _command) internal returns (string memory stdout_) { + stdout_ = bash({ _command: _command, _allowEmpty: false }); + } + + /// @notice Executes a bash command in a subprocess and returns its output as a string. Will 'optionally' revert if + /// the command returns no output. + /// @param _command The bash command to execute + /// @param _allowEmpty Allow empty output. + function bash(string memory _command, bool _allowEmpty) internal returns (string memory stdout_) { + string[] memory command = new string[](3); + command[0] = "bash"; + command[1] = "-c"; + command[2] = _command; + stdout_ = string(run({ _command: command, _allowEmpty: _allowEmpty })); + } + /// @notice Run a command in a subprocess. Fails if no output is returned. /// @param _command Command to run. function run(string[] memory _command) internal returns (bytes memory stdout_) { diff --git a/packages/contracts-bedrock/scripts/libraries/LibStateDiff.sol b/packages/contracts-bedrock/scripts/libraries/StateDiff.sol similarity index 99% rename from packages/contracts-bedrock/scripts/libraries/LibStateDiff.sol rename to packages/contracts-bedrock/scripts/libraries/StateDiff.sol index 2b6f38a9afa77..4b4cacab42f24 100644 --- a/packages/contracts-bedrock/scripts/libraries/LibStateDiff.sol +++ b/packages/contracts-bedrock/scripts/libraries/StateDiff.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.15; import { stdJson } from "forge-std/StdJson.sol"; import { VmSafe } from "forge-std/Vm.sol"; -/// @title LibStateDiff +/// @title StateDiff /// @author refcell /// @notice Library to write StateDiff output to json. -library LibStateDiff { +library StateDiff { /// @notice Accepts an array of AccountAccess structs from the Vm and encodes them as a json string. /// @param _accountAccesses Array of AccountAccess structs. /// @return serialized_ string diff --git a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol b/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol deleted file mode 100644 index 9e5bb96cfe319..0000000000000 --- a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { console } from "forge-std/console.sol"; -import { Script } from "forge-std/Script.sol"; -import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol"; -import { Predeploys } from "src/libraries/Predeploys.sol"; -import { IFeeVault } from "src/L2/interfaces/IFeeVault.sol"; - -/// @title FeeVaultWithdrawal -/// @notice A script to make it very simple to withdraw from the fee vaults. -/// The usage is as follows: -/// $ forge script scripts/FeeVaultWithdrawal.s.sol \ -/// --rpc-url $ETH_RPC_URL --broadcast \ -/// --private-key $PRIVATE_KEY -contract FeeVaultWithdrawal is Script { - IMulticall3 private constant multicall = IMulticall3(MULTICALL3_ADDRESS); - IMulticall3.Call3[] internal calls; - - /// @notice The entrypoint function. Determines which FeeVaults can be withdrawn from and then - /// will send the transaction via Multicall3 to withdraw all FeeVaults. - function run() external { - require(address(multicall).code.length > 0); - - address[] memory vaults = new address[](3); - vaults[0] = Predeploys.SEQUENCER_FEE_WALLET; - vaults[1] = Predeploys.BASE_FEE_VAULT; - vaults[2] = Predeploys.L1_FEE_VAULT; - - for (uint256 i; i < vaults.length; i++) { - address vault = vaults[i]; - bool shouldCall = canWithdrawal(vault); - if (shouldCall) { - calls.push( - IMulticall3.Call3({ - target: vault, - allowFailure: false, - callData: abi.encodeWithSelector(IFeeVault.withdraw.selector) - }) - ); - - address recipient = IFeeVault(payable(vault)).RECIPIENT(); - uint256 balance = vault.balance; - log(balance, recipient, vault); - } else { - string memory logline = - string.concat(vm.toString(vault), " does not have a large enough balance to withdraw."); - console.log(logline); - } - } - - if (calls.length > 0) { - vm.broadcast(); - multicall.aggregate3(calls); - console.log("Success."); - } - } - - /// @notice Checks whether or not a FeeVault can be withdrawn. The balance of the account must - /// be larger than the `MIN_WITHDRAWAL_AMOUNT`. - function canWithdrawal(address _vault) internal view returns (bool) { - uint256 minWithdrawalAmount = IFeeVault(payable(_vault)).MIN_WITHDRAWAL_AMOUNT(); - uint256 balance = _vault.balance; - return balance >= minWithdrawalAmount; - } - - /// @notice Logs the information relevant to the user. - function log(uint256 _balance, address _recipient, address _vault) internal pure { - string memory logline = string.concat( - "Withdrawing ", vm.toString(_balance), " to ", vm.toString(_recipient), " from ", vm.toString(_vault) - ); - console.log(logline); - } -} diff --git a/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh b/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh old mode 100644 new mode 100755 index 6278e37ffa74b..f00fa6b17641b --- a/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh +++ b/packages/contracts-bedrock/scripts/ops/calculate-checksum.sh @@ -14,12 +14,13 @@ cd "$CONTRACTS_DIR" echoerr "> Calculating contracts checksum..." find . -type f -name '*.sol' -exec sha256sum {} + > manifest.txt -sha256sum semver-lock.json >> manifest.txt +sha256sum snapshots/semver-lock.json >> manifest.txt sha256sum foundry.toml >> manifest.txt # need to specify the locale to ensure consistent sorting across platforms LC_ALL=C sort -o manifest.txt manifest.txt checksum=$(sha256sum manifest.txt | awk '{print $1}') rm manifest.txt +echoerr "> Checksum: $checksum" echoerr "> Done." echo -n "$checksum" \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh b/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh index 309b2d818edaa..9e04ea8d0fdc2 100644 --- a/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh +++ b/packages/contracts-bedrock/scripts/ops/publish-artifacts.sh @@ -25,6 +25,7 @@ fi checksum=$(bash scripts/ops/calculate-checksum.sh) +echoerr "> Checksum: $checksum" echoerr "> Checking for existing artifacts..." exists=$(curl -s -o /dev/null --fail -LI "https://storage.googleapis.com/$DEPLOY_BUCKET/artifacts-v1-$checksum.tar.gz" || echo "fail") @@ -43,7 +44,11 @@ else tar="tar" fi -"$tar" -czf "$archive_name" artifacts forge-artifacts cache +rm -f COMMIT +commit=$(git rev-parse HEAD) +echo "$commit" > COMMIT + +"$tar" -czf "$archive_name" artifacts forge-artifacts cache COMMIT du -sh "$archive_name" | awk '{$1=$1};1' # trim leading whitespace echoerr "> Done." @@ -51,4 +56,5 @@ echoerr "> Uploading artifacts to GCS..." gcloud storage cp "$archive_name" "gs://$DEPLOY_BUCKET/$archive_name" echoerr "> Done." -rm "$archive_name" \ No newline at end of file +rm "$archive_name" +rm COMMIT \ No newline at end of file diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json deleted file mode 100644 index 724d889f53264..0000000000000 --- a/packages/contracts-bedrock/semver-lock.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "src/L1/DataAvailabilityChallenge.sol": { - "initCodeHash": "0xbd00d6568abab3e7fc211c40d682862242f25493010a4a097bd1f3b45c8c87c3", - "sourceCodeHash": "0x58b587034a67b4bb718abbaded8ac23b082c0971105874bcc42c23f051c67f6e" - }, - "src/L1/DelayedVetoable.sol": { - "initCodeHash": "0x9fe8ade6f6332262ff1f3539ac0bf57660edbad3cf4c4cb230c2ddac18aa0a3f", - "sourceCodeHash": "0x30e83a535ef27b2e900c831c4e1a4ec2750195350011c4fdacda1da9db2d167b" - }, - "src/L1/L1CrossDomainMessenger.sol": { - "initCodeHash": "0x2e9cb3ceb5e55341b311f0666ef7655df4fafae75afdfbcd701cd9c9b2b017d5", - "sourceCodeHash": "0x848ec3774be17bcc8ba65a23d08e35e979b3f39f9d2ac8a810188f945c69c9ea" - }, - "src/L1/L1ERC721Bridge.sol": { - "initCodeHash": "0x63dc4da75200f4b968f57e27e81834e6eb3f6625826410882526ab1eec7847ff", - "sourceCodeHash": "0xfec29cfbb7aa05473e32a6c2484deebfc1ff50c0e08c42e8ee70696ad701ceaa" - }, - "src/L1/L1StandardBridge.sol": { - "initCodeHash": "0x2868b09ecbe9f2bbc885605c2886b4c79f1c8e4171626c63776603b1b84698a8", - "sourceCodeHash": "0xc03da137b3ea72e0109fb284229283b21a0303104afbe37d2fe86ad806392a7f" - }, - "src/L1/L2OutputOracle.sol": { - "initCodeHash": "0x1182bfb87c4ab399b912ca7fe18cdbf4b24c414e078fb0a55bd3c44d442d3ed1", - "sourceCodeHash": "0x4132ff37d267cb12224b75ea806c0aa7d25407b0d66ce526d7fcda8f7d223882" - }, - "src/L1/OPContractsManager.sol": { - "initCodeHash": "0xd58cb3978affc5c1457cdd498ff8420c90aef804d4c3b62cf42ab2691986d6d2", - "sourceCodeHash": "0x7bfa6eff76176649fe600303cd60009a0f6e282cbaec55836b5ea1f8875cbeb5" - }, - "src/L1/OptimismPortal.sol": { - "initCodeHash": "0xbe2c0c81b3459014f287d8c89cdc0d27dde5d1f44e5d024fa1e4773ddc47c190", - "sourceCodeHash": "0xb3dd8068477dd304ef1562acf69c2ce460b08cc36aef53b5bbe489fd7d992104" - }, - "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0xc94c609e04ab8ffee880806550dffff53478dfffdfb079f7c487abe0e2996f3c", - "sourceCodeHash": "0x3fb97859f66c078573753b6ba5ec370449ab03b8eca9e7779fce8db5bb23b7c0" - }, - "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0xfeaa67ccd652bda9103fea507e4357b2bd4e93210b03ff85eb357d7145f1606c", - "sourceCodeHash": "0x6401b81f04093863557ef46192f56793daa0d412618065383ab353b2ed2929d8" - }, - "src/L1/ProtocolVersions.sol": { - "initCodeHash": "0xefd4806e8737716d5d2022ca2e9e9fba0a0cb5714b026166b58e472222c7d15f", - "sourceCodeHash": "0x15205131bf420aa6d03c558bb75dd49cd7439caed7ccdcbfd89c4170a48c94f5" - }, - "src/L1/SuperchainConfig.sol": { - "initCodeHash": "0xfca12d9016c746e5c275b186e0ca40cfd65cf45a5665aab7589a669fea3abb47", - "sourceCodeHash": "0x39489a85bc3a5c8560f82d41b31bf7fe22f5b648f4ed538f61695a73092ea9eb" - }, - "src/L1/SystemConfig.sol": { - "initCodeHash": "0x2fc36af5b3c493a423a19e0b597f6ff230b756b861b68099f3192d69b6088dc0", - "sourceCodeHash": "0x06a50ac992175fdb434b13e8461893e83862c23ce399e697e6e8109728ad1a3d" - }, - "src/L1/SystemConfigInterop.sol": { - "initCodeHash": "0x7515e5ed1266412a8c2d27d99aba6266fda2fc9068c20f0b7e6b555ee5073c91", - "sourceCodeHash": "0x441d1e3e8e987f829f55996b5b6c850da8c59ad48f09cf7e0a69a1fa559d42a2" - }, - "src/L2/BaseFeeVault.sol": { - "initCodeHash": "0xbf49824cf37e201181484a8a423fcad8f504dc925921a2b28e83398197858dec", - "sourceCodeHash": "0x983e8e248c61e362ba6a01dd2e217a535c9bb828dc0b4421f5f27e0577f2e14c" - }, - "src/L2/CrossL2Inbox.sol": { - "initCodeHash": "0x66b052adce7e9194d054952d67d08b53964120067600358243ec86c85b90877b", - "sourceCodeHash": "0x38e6127ec6be99eb8c38c2c9d6e82761b33dde446bba250dc2c1b84983449e4e" - }, - "src/L2/ETHLiquidity.sol": { - "initCodeHash": "0x713c18f95a6a746d0703f475f3ae10c106c9b9ecb64d881a2e61b8969b581371", - "sourceCodeHash": "0x0b6afdc52d1ae88d9e4bbb5dc00920e7a6bd1e9d6595bfdbae64874190f39df0" - }, - "src/L2/GasPriceOracle.sol": { - "initCodeHash": "0x03947f33b80774b92214083374262fe6a4defa61da548391b44d471f2e87e9e7", - "sourceCodeHash": "0x4f21025d4b5c9c74cf7040db6f8e9ce605b82931e3012fee51d3f5d9fbd7b73f" - }, - "src/L2/L1Block.sol": { - "initCodeHash": "0xd12353c5bf71c6765cc9292eecf262f216e67f117f4ba6287796a5207dbca00f", - "sourceCodeHash": "0xfe3a9585d9bfca8428e12759cab68a3114374e5c37371cfe08bb1976a9a5a041" - }, - "src/L2/L1BlockInterop.sol": { - "initCodeHash": "0x77b3b2151fe14ea36a640469115a5e4de27f7654a9606a9d0701522c6a4ad887", - "sourceCodeHash": "0x7417677643e1df1ae1782513b94c7821097b9529d3f8626c3bcb8b3a9ae0d180" - }, - "src/L2/L1FeeVault.sol": { - "initCodeHash": "0xbf49824cf37e201181484a8a423fcad8f504dc925921a2b28e83398197858dec", - "sourceCodeHash": "0xc7cda130f2bb3648e04d5a480082aa1789e16456c1280954d822b05d30100b2d" - }, - "src/L2/L2CrossDomainMessenger.sol": { - "initCodeHash": "0xc496495496b96ea0eaf417c5e56b295836c12db3e6aafe2e607563e7a50b5b65", - "sourceCodeHash": "0x56edf0f36366326a92722ae3c7502bce3d80b2ee5e354181dc09ba801437a488" - }, - "src/L2/L2ERC721Bridge.sol": { - "initCodeHash": "0x558fff5939a26b9d5d86e6b907d9dd9c7c917eaef7657a8b5acfeb58de1442f0", - "sourceCodeHash": "0xca9acd19fd5f42e6a7a5b1de6359f2d841814fb54d377664c2fe9c3f9c6be34a" - }, - "src/L2/L2StandardBridge.sol": { - "initCodeHash": "0x651eed10044d0b19b7e4eba864345df15e252be1401f39a552ec0d2f9c4df064", - "sourceCodeHash": "0xb55e58b5d4912edf05026878a5f5ac8019372212ed2a77843775d595fbf51b84" - }, - "src/L2/L2StandardBridgeInterop.sol": { - "initCodeHash": "0x9bc28e8511a4593362c2517ff90b26f9c1ecee382cce3950b47ca08892a872ef", - "sourceCodeHash": "0x6c814f4536d9fb8f384ed2195957f868abd15252e36d6dd243f3d60349a61994" - }, - "src/L2/L2ToL1MessagePasser.sol": { - "initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d", - "sourceCodeHash": "0xd08a2e6514dbd44e16aa312a1b27b2841a9eab5622cbd05a39c30f543fad673c" - }, - "src/L2/L2ToL2CrossDomainMessenger.sol": { - "initCodeHash": "0x6f19eb8ff0950156b65cd92872240c0153ac5f3b6f0861d57bf561fdbcacbeac", - "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" - }, - "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x965af580568bad2b47d04c6ea536490aa263e9fcb5fb43e6c8bc00929fda3df5", - "sourceCodeHash": "0x9de349519900b1051f45d507b2fac1cf3f3ae8e2cfb1ceb56875a7ace1cb6ab8" - }, - "src/L2/OptimismSuperchainERC20Beacon.sol": { - "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", - "sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e" - }, - "src/L2/OptimismSuperchainERC20Factory.sol": { - "initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", - "sourceCodeHash": "0x1e02d78a4e7ee93a07f7af7a78fe1773d0e87711f23a4ccd10a8692b47644a34" - }, - "src/L2/SequencerFeeVault.sol": { - "initCodeHash": "0xcaadbf08057b5d47f7704257e9385a29e42a7a08c818646d109c5952d3d35218", - "sourceCodeHash": "0x05bbc6039e5a9ff38987e7b9b89c69e2ee8aa4b7ca20dd002ea1bbd3d70f27f3" - }, - "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0x4ccd25f37a816205bc26f8532afa66e02f2b36ca7b7404d0fa48a4313ed16f0c", - "sourceCodeHash": "0xd186614f1515fa3ba2f43e401e639bfa3159603954e39a51769e9b57ad19a3fd" - }, - "src/L2/WETH.sol": { - "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", - "sourceCodeHash": "0x2ab6be69795109a1ee04c5693a34d6ce0ff90b62e404cdeb18178bab18d06784" - }, - "src/cannon/MIPS.sol": { - "initCodeHash": "0x3e426acc53ebd6ad01037ea321410fab2df08e1d1183195c15be9ff48fef4d44", - "sourceCodeHash": "0xaf7416f27db1b393092f51d290a29293184105bc5f0d89cd6048f687cebc7d69" - }, - "src/cannon/MIPS2.sol": { - "initCodeHash": "0xbb203b0d83efddfa0f664dbc63ec55844318b48fe8133758307f64e87c892a47", - "sourceCodeHash": "0x16614cc0e6abf7e81e1e5dc2c0773ee7101cb38af40e0907a8800ca7eddd3b5a" - }, - "src/cannon/PreimageOracle.sol": { - "initCodeHash": "0x64ea814bf9769257c91da57928675d3f8462374b0c23bdf860ccfc79f41f7801", - "sourceCodeHash": "0xac290a77986d54efe404c73ee58d11429e1b1fb47e4d06d4c38b6ecc20751d78" - }, - "src/dispute/AnchorStateRegistry.sol": { - "initCodeHash": "0x13d00eef8c3f769863fc766180acc8586f5da309ca0a098e67d4d90bd3243341", - "sourceCodeHash": "0x39a23c91d4c5380d285f49660b858d39f3fa27bdbfbc72e0e14587e7c57dfae9" - }, - "src/dispute/DelayedWETH.sol": { - "initCodeHash": "0x835b322de7d5c84b415e99f2cb1000411df18995b5476f2116ac6f897f2d0910", - "sourceCodeHash": "0xdbd64724b73f8f9d6f1cc72bb662a99b9955ab72950a8f6ffeb1d2454997f60b" - }, - "src/dispute/DisputeGameFactory.sol": { - "initCodeHash": "0xb91623cca41e63e6e5a75c681b0d9ef7cb8bf68e7ff5e202a217629899fae099", - "sourceCodeHash": "0xc8f21c777b2c5a37c2d2f92e8eeceba3b231b500a7d9cb0b607b774478f8be6b" - }, - "src/dispute/FaultDisputeGame.sol": { - "initCodeHash": "0x04e6c36ee49f7744e5277c1d83ba78616ef3e3cef62406d32e2d9c72bca2010a", - "sourceCodeHash": "0x9b9e971748d253790b3ce9da0b1cbbcb6df77bb252ee2d1c5088bb6bae6491aa" - }, - "src/legacy/DeployerWhitelist.sol": { - "initCodeHash": "0x0b8177ed75b69eddbb9ce6537683f69a9935efed86a1d6faa8feaafbd151c1bd", - "sourceCodeHash": "0xc8fe9571fcf8fcb51a4dcb00ffa97f43a9ce811c323c4926e710b28c90a9005f" - }, - "src/legacy/L1BlockNumber.sol": { - "initCodeHash": "0x542955f1a84b304eaf291f76633b03e4c87c2654f7eff46c3bea94d27346ea1f", - "sourceCodeHash": "0x898c239e6367a0971a075df18030a033cdada26983fa8a5cd6e7b88ec90d4958" - }, - "src/legacy/LegacyMessagePasser.sol": { - "initCodeHash": "0xefc6ed9e325c2d614ea0d28c3eabfff1b345f7c6054e90253c6a091c29508267", - "sourceCodeHash": "0xaa08a61448f485b277af57251d2089cc6a80ce0a763bf7184d48ffed5034ef69" - }, - "src/periphery/op-nft/AttestationStation.sol": { - "initCodeHash": "0x2e665d9ee554430980f64bcb6d2611a1cb03dbacfd58bb0d6f5d32951a267bde", - "sourceCodeHash": "0xe0bc805b22c7d04b5a9444cddd4c0e1bcb3006c69c03610494277ab2cc83f553" - }, - "src/periphery/op-nft/Optimist.sol": { - "initCodeHash": "0x8fccdef5fb6e6d51215b39acc449faad8ba15416699c9b3af77866f4297805a3", - "sourceCodeHash": "0xfa9354827b642803e10415ed30ca789be1bd23d88fac14f7adaa65c6eb1c1643" - }, - "src/periphery/op-nft/OptimistAllowlist.sol": { - "initCodeHash": "0x166dd3fc18cb238895f2faa7fdd635af48ce2c54e21ed2d6dae857c3731c4d6c", - "sourceCodeHash": "0x3a5f61046f729c9a70274b8b2a739382987ec5eb77705b259e8a3210a5f43462" - }, - "src/periphery/op-nft/OptimistInviter.sol": { - "initCodeHash": "0x28dfa6676702a7abd19609cc773158d1f958210bc0a38c008d67a002dc1df862", - "sourceCodeHash": "0x3a0a294932d6deba043f6a2b46b4e8477ee96e7fb054d7e7229a43ce4352c68d" - }, - "src/safe/DeputyGuardianModule.sol": { - "initCodeHash": "0x308212d163aad169a5e42ce703a1ce36f5425ad96037850c0747177041f6596e", - "sourceCodeHash": "0xde1a289c1cb0bf92138daf8f3db7457be2f84bedaa111b536f646dd6e121718c" - }, - "src/safe/LivenessGuard.sol": { - "initCodeHash": "0x9ac0b039b1591f7c00cf11cb758d118c9b42e6e08250b619d6b6fd605a43d5ee", - "sourceCodeHash": "0xc1a968b0c6fbc4d82c2821c917b273feaaa224d258886b394416e84ee250d026" - }, - "src/safe/LivenessModule.sol": { - "initCodeHash": "0xcfccdd9e423c95a0ddc6e09ccb6333d5fc8429ed2b8fc872f1290d392ae13aad", - "sourceCodeHash": "0xd1479c60087f352385b6d5379ef3cc07839f671d617626b4c94ece91da781ef2" - }, - "src/universal/OptimismMintableERC20.sol": { - "initCodeHash": "0x28c88484e1932253d6d12954492ac8a70744dc15c84429089af9944e5b158fd9", - "sourceCodeHash": "0x740b4043436d1b314ee3ba145acfcde60b6abd8416ea594f2b8e890b5d0bce6b" - }, - "src/universal/OptimismMintableERC20Factory.sol": { - "initCodeHash": "0x9cd4102d3ca811d5dc67ae99ce7de95812264575a789f96a6057600e55dcab64", - "sourceCodeHash": "0xc70c8c11d6e754eabe746bbee47a5e1051f71f7a83913f62ebcce8db989a1357" - }, - "src/universal/OptimismMintableERC721.sol": { - "initCodeHash": "0xec037be7fc28e072944b0a9e55d4278b92d6c68ccb41049ab52eafca59c6e023", - "sourceCodeHash": "0x5ea7c1b0cef5609f25c4193f5795fc9ce8f3ae08dbbf2945afe38e5af58f32c3" - }, - "src/universal/OptimismMintableERC721Factory.sol": { - "initCodeHash": "0x63d2ceafd3f3b3b54e31749574563e8022fef9c6da7bb8c7a113b3dbf571cfa2", - "sourceCodeHash": "0x705e270925fcad14e805b5ec1bbbb9e78b5b44e3b128f284b0113a4d68c82ef6" - }, - "src/universal/StorageSetter.sol": { - "initCodeHash": "0x21b3059e9b13b330f76d02b61f61dcfa3abf3517a0b56afa0895c4b8291740bf", - "sourceCodeHash": "0xc1ea12a87e3a7ef9c950f0a41a4e35b60d4d9c4c816ff671dbfca663861c16f4" - }, - "src/vendor/eas/EAS.sol": { - "initCodeHash": "0xf96d1ebc530ed95e2dffebcfa2b4a1f18103235e6352d97838b77b7a2c14567b", - "sourceCodeHash": "0xbeca762929db37f1c7a2067e136c616f563ca18e85871ad7ae2d3ff55a16e6cb" - }, - "src/vendor/eas/SchemaRegistry.sol": { - "initCodeHash": "0x06ae2c0b39c215b7fa450d382916ce6f5c6f9f2d630e572db6b72d688255b3fd", - "sourceCodeHash": "0xa014d9c992f439dee8221e065828c3326ca2c4f5db0e83431c64c20f7e51ec14" - } -} \ No newline at end of file diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/snapshots/.gas-snapshot similarity index 87% rename from packages/contracts-bedrock/.gas-snapshot rename to packages/contracts-bedrock/snapshots/.gas-snapshot index da67af9f81fca..2ab3157a714ee 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/snapshots/.gas-snapshot @@ -4,14 +4,14 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369245) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967385) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564368) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076583) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512723) -GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369280) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967465) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564398) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076613) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467098) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512802) +GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72664) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921) -GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68422) +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68986) +GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155565) \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/AttestationStation.json b/packages/contracts-bedrock/snapshots/abi/AttestationStation.json deleted file mode 100644 index ba7d5f9759e7e..0000000000000 --- a/packages/contracts-bedrock/snapshots/abi/AttestationStation.json +++ /dev/null @@ -1,128 +0,0 @@ -[ - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "about", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "key", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "val", - "type": "bytes" - } - ], - "internalType": "struct AttestationStation.AttestationData[]", - "name": "_attestations", - "type": "tuple[]" - } - ], - "name": "attest", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_about", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "_key", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "_val", - "type": "bytes" - } - ], - "name": "attest", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "attestations", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "about", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "key", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "val", - "type": "bytes" - } - ], - "name": "AttestationCreated", - "type": "event" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/CrossL2Inbox.json b/packages/contracts-bedrock/snapshots/abi/CrossL2Inbox.json index 6f8c10e82eedf..47eeab3f07f14 100644 --- a/packages/contracts-bedrock/snapshots/abi/CrossL2Inbox.json +++ b/packages/contracts-bedrock/snapshots/abi/CrossL2Inbox.json @@ -55,7 +55,7 @@ "type": "uint256" } ], - "internalType": "struct ICrossL2Inbox.Identifier", + "internalType": "struct Identifier", "name": "_id", "type": "tuple" }, @@ -164,7 +164,7 @@ "type": "uint256" } ], - "internalType": "struct ICrossL2Inbox.Identifier", + "internalType": "struct Identifier", "name": "_id", "type": "tuple" }, @@ -230,7 +230,7 @@ } ], "indexed": false, - "internalType": "struct ICrossL2Inbox.Identifier", + "internalType": "struct Identifier", "name": "id", "type": "tuple" } diff --git a/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json b/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json index a84394e6b0036..a6f0dd660468f 100644 --- a/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json @@ -22,12 +22,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -70,7 +70,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/FaultDisputeGame.json b/packages/contracts-bedrock/snapshots/abi/FaultDisputeGame.json index e1e59c38701cf..a2f02cce13bd8 100644 --- a/packages/contracts-bedrock/snapshots/abi/FaultDisputeGame.json +++ b/packages/contracts-bedrock/snapshots/abi/FaultDisputeGame.json @@ -2,54 +2,61 @@ { "inputs": [ { - "internalType": "GameType", - "name": "_gameType", - "type": "uint32" - }, - { - "internalType": "Claim", - "name": "_absolutePrestate", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_maxGameDepth", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_splitDepth", - "type": "uint256" - }, - { - "internalType": "Duration", - "name": "_clockExtension", - "type": "uint64" - }, - { - "internalType": "Duration", - "name": "_maxClockDuration", - "type": "uint64" - }, - { - "internalType": "contract IBigStepper", - "name": "_vm", - "type": "address" - }, - { - "internalType": "contract IDelayedWETH", - "name": "_weth", - "type": "address" - }, - { - "internalType": "contract IAnchorStateRegistry", - "name": "_anchorStateRegistry", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_l2ChainId", - "type": "uint256" + "components": [ + { + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "maxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "splitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "clockExtension", + "type": "uint64" + }, + { + "internalType": "Duration", + "name": "maxClockDuration", + "type": "uint64" + }, + { + "internalType": "contract IBigStepper", + "name": "vm", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "weth", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry", + "type": "address" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + } + ], + "internalType": "struct FaultDisputeGame.GameConstructorParams", + "name": "_params", + "type": "tuple" } ], "stateMutability": "nonpayable", diff --git a/packages/contracts-bedrock/snapshots/abi/L1Block.json b/packages/contracts-bedrock/snapshots/abi/L1Block.json index 020c9e942c757..a32eff778d6f2 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1Block.json +++ b/packages/contracts-bedrock/snapshots/abi/L1Block.json @@ -77,6 +77,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_historyNumber", + "type": "uint256" + } + ], + "name": "blockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasPayingToken", @@ -134,6 +153,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "historySize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, { "inputs": [], "name": "isCustomGasToken", diff --git a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json index ab089f0cec555..1d54b5af228ca 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json @@ -77,6 +77,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_historyNumber", + "type": "uint256" + } + ], + "name": "blockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "dependencySetSize", @@ -154,6 +173,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "historySize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, { "inputs": [], "name": "isCustomGasToken", diff --git a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json index 2676f90b04917..9f51cc1c3a271 100644 --- a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json @@ -1,4 +1,22 @@ [ + { + "inputs": [], + "name": "crossDomainMessageContext", + "outputs": [ + { + "internalType": "address", + "name": "sender_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "source_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "crossDomainMessageSender", @@ -81,7 +99,7 @@ "type": "uint256" } ], - "internalType": "struct ICrossL2Inbox.Identifier", + "internalType": "struct Identifier", "name": "_id", "type": "tuple" }, @@ -157,31 +175,6 @@ "stateMutability": "view", "type": "function" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "source", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "messageNonce", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "messageHash", - "type": "bytes32" - } - ], - "name": "FailedRelayedMessage", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -254,6 +247,11 @@ "name": "IdOriginNotL2ToL2CrossDomainMessenger", "type": "error" }, + { + "inputs": [], + "name": "InvalidChainId", + "type": "error" + }, { "inputs": [], "name": "MessageAlreadyRelayed", @@ -288,5 +286,10 @@ "inputs": [], "name": "ReentrantCall", "type": "error" + }, + { + "inputs": [], + "name": "TargetCallFailed", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/MIPS2.json b/packages/contracts-bedrock/snapshots/abi/MIPS2.json index 2294e23e754e5..348868bfba0ad 100644 --- a/packages/contracts-bedrock/snapshots/abi/MIPS2.json +++ b/packages/contracts-bedrock/snapshots/abi/MIPS2.json @@ -45,7 +45,7 @@ "outputs": [ { "internalType": "bytes32", - "name": "", + "name": "postState_", "type": "bytes32" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/MIPS64.json b/packages/contracts-bedrock/snapshots/abi/MIPS64.json new file mode 100644 index 0000000000000..a12c96c7a70db --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/MIPS64.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [ + { + "internalType": "contract IPreimageOracle", + "name": "_oracle", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract IPreimageOracle", + "name": "oracle_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_stateData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "_proof", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_localContext", + "type": "bytes32" + } + ], + "name": "step", + "outputs": [ + { + "internalType": "bytes32", + "name": "postState_", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "InvalidExitedValue", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMemoryProof", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPC", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRMWInstruction", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSecondMemoryProof", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index 7c478feb235d4..b5758eca610f7 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -10,6 +10,110 @@ "internalType": "contract IProtocolVersions", "name": "_protocolVersions", "type": "address" + }, + { + "internalType": "string", + "name": "_l1ContractsRelease", + "type": "string" + }, + { + "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": "anchorStateRegistry", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "_blueprints", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "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": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "_implementations", + "type": "tuple" } ], "stateMutability": "nonpayable", @@ -298,138 +402,68 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "string", - "name": "", - "type": "string" - } - ], + "inputs": [], "name": "implementations", "outputs": [ - { - "internalType": "address", - "name": "logic", - "type": "address" - }, - { - "internalType": "bytes4", - "name": "initializer", - "type": "bytes4" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ { "components": [ { - "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": "anchorStateRegistry", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame2", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Blueprints", - "name": "blueprints", - "type": "tuple" + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" }, { - "components": [ - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "components": [ - { - "internalType": "address", - "name": "logic", - "type": "address" - }, - { - "internalType": "bytes4", - "name": "initializer", - "type": "bytes4" - } - ], - "internalType": "struct OPContractsManager.Implementation", - "name": "info", - "type": "tuple" - } - ], - "internalType": "struct OPContractsManager.ImplementationSetter[]", - "name": "setters", - "type": "tuple[]" + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" }, { - "internalType": "string", - "name": "release", - "type": "string" + "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": "bool", - "name": "isLatest", - "type": "bool" + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.InitializerInputs", - "name": "_initializerInputs", + "internalType": "struct OPContractsManager.Implementations", + "name": "", "type": "tuple" } ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "latestRelease", + "name": "l1ContractsRelease", "outputs": [ { "internalType": "string", @@ -529,19 +563,6 @@ "name": "Deployed", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json index 7c478feb235d4..b5758eca610f7 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json @@ -10,6 +10,110 @@ "internalType": "contract IProtocolVersions", "name": "_protocolVersions", "type": "address" + }, + { + "internalType": "string", + "name": "_l1ContractsRelease", + "type": "string" + }, + { + "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": "anchorStateRegistry", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "_blueprints", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "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": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "_implementations", + "type": "tuple" } ], "stateMutability": "nonpayable", @@ -298,138 +402,68 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "string", - "name": "", - "type": "string" - } - ], + "inputs": [], "name": "implementations", "outputs": [ - { - "internalType": "address", - "name": "logic", - "type": "address" - }, - { - "internalType": "bytes4", - "name": "initializer", - "type": "bytes4" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ { "components": [ { - "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": "anchorStateRegistry", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame2", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Blueprints", - "name": "blueprints", - "type": "tuple" + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" }, { - "components": [ - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "components": [ - { - "internalType": "address", - "name": "logic", - "type": "address" - }, - { - "internalType": "bytes4", - "name": "initializer", - "type": "bytes4" - } - ], - "internalType": "struct OPContractsManager.Implementation", - "name": "info", - "type": "tuple" - } - ], - "internalType": "struct OPContractsManager.ImplementationSetter[]", - "name": "setters", - "type": "tuple[]" + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" }, { - "internalType": "string", - "name": "release", - "type": "string" + "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": "bool", - "name": "isLatest", - "type": "bool" + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" } ], - "internalType": "struct OPContractsManager.InitializerInputs", - "name": "_initializerInputs", + "internalType": "struct OPContractsManager.Implementations", + "name": "", "type": "tuple" } ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "latestRelease", + "name": "l1ContractsRelease", "outputs": [ { "internalType": "string", @@ -529,19 +563,6 @@ "name": "Deployed", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 6eb57764a8cb8..fbe697b864d86 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -102,6 +102,42 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "crosschainBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "crosschainMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "decimals", @@ -236,29 +272,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "relayERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "remoteToken", @@ -272,29 +285,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_chainId", - "type": "uint256" - } - ], - "name": "sendERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -437,7 +427,7 @@ { "indexed": true, "internalType": "address", - "name": "account", + "name": "from", "type": "address" }, { @@ -450,26 +440,13 @@ "name": "Burn", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" - } - ], - "name": "Initialized", - "type": "event" - }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", - "name": "account", + "name": "from", "type": "address" }, { @@ -477,20 +454,20 @@ "internalType": "uint256", "name": "amount", "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" } ], - "name": "Mint", + "name": "CrosschainBurn", "type": "event" }, { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, { "indexed": true, "internalType": "address", @@ -504,24 +481,31 @@ "type": "uint256" }, { - "indexed": false, - "internalType": "uint256", - "name": "source", - "type": "uint256" + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" } ], - "name": "RelayERC20", + "name": "CrosschainMint", "type": "event" }, { "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ { "indexed": true, "internalType": "address", @@ -533,15 +517,9 @@ "internalType": "uint256", "name": "amount", "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destination", - "type": "uint256" } ], - "name": "SendERC20", + "name": "Mint", "type": "event" }, { @@ -579,11 +557,6 @@ "name": "AllowanceUnderflow", "type": "error" }, - { - "inputs": [], - "name": "CallerNotL2ToL2CrossDomainMessenger", - "type": "error" - }, { "inputs": [], "name": "InsufficientAllowance", @@ -594,11 +567,6 @@ "name": "InsufficientBalance", "type": "error" }, - { - "inputs": [], - "name": "InvalidCrossDomainSender", - "type": "error" - }, { "inputs": [], "name": "InvalidInitialization", @@ -616,7 +584,7 @@ }, { "inputs": [], - "name": "OnlyBridge", + "name": "Permit2AllowanceIsFixedAtInfinity", "type": "error" }, { @@ -629,6 +597,11 @@ "name": "TotalSupplyOverflow", "type": "error" }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, { "inputs": [], "name": "ZeroAddress", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Beacon.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Beacon.json index 0bdfc64ed2fe5..a06b5b5d140ec 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Beacon.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Beacon.json @@ -1,15 +1,4 @@ [ - { - "inputs": [ - { - "internalType": "address", - "name": "_implementation", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, { "inputs": [], "name": "implementation", @@ -20,7 +9,7 @@ "type": "address" } ], - "stateMutability": "view", + "stateMutability": "pure", "type": "function" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json index fdf212052c0e9..f6020300b036c 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json @@ -37,7 +37,7 @@ "inputs": [ { "internalType": "address", - "name": "_superchainToken", + "name": "_localToken", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/OptimistAllowlist.json b/packages/contracts-bedrock/snapshots/abi/OptimistAllowlist.json deleted file mode 100644 index 87ac8f8a014f8..0000000000000 --- a/packages/contracts-bedrock/snapshots/abi/OptimistAllowlist.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract AttestationStation", - "name": "_attestationStation", - "type": "address" - }, - { - "internalType": "address", - "name": "_allowlistAttestor", - "type": "address" - }, - { - "internalType": "address", - "name": "_coinbaseQuestAttestor", - "type": "address" - }, - { - "internalType": "address", - "name": "_optimistInviter", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ALLOWLIST_ATTESTOR", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ATTESTATION_STATION", - "outputs": [ - { - "internalType": "contract AttestationStation", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "COINBASE_QUEST_ATTESTOR", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "OPTIMIST_CAN_MINT_ATTESTATION_KEY", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "OPTIMIST_INVITER", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimer", - "type": "address" - } - ], - "name": "isAllowedToMint", - "outputs": [ - { - "internalType": "bool", - "name": "allowed_", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimistInviter.json b/packages/contracts-bedrock/snapshots/abi/OptimistInviter.json deleted file mode 100644 index a5300b20a3c10..0000000000000 --- a/packages/contracts-bedrock/snapshots/abi/OptimistInviter.json +++ /dev/null @@ -1,282 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "_inviteGranter", - "type": "address" - }, - { - "internalType": "contract AttestationStation", - "name": "_attestationStation", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ATTESTATION_STATION", - "outputs": [ - { - "internalType": "contract AttestationStation", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "CAN_INVITE_ATTESTATION_KEY", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "CLAIMABLE_INVITE_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "EIP712_VERSION", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "INVITE_GRANTER", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MIN_COMMITMENT_PERIOD", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimer", - "type": "address" - }, - { - "components": [ - { - "internalType": "address", - "name": "issuer", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - } - ], - "internalType": "struct OptimistInviter.ClaimableInvite", - "name": "_claimableInvite", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "_signature", - "type": "bytes" - } - ], - "name": "claimInvite", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_commitment", - "type": "bytes32" - } - ], - "name": "commitInvite", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "commitmentTimestamps", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "inviteCounts", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "_accounts", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "_inviteCount", - "type": "uint256" - } - ], - "name": "setInviteCounts", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "usedNonces", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "issuer", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "claimer", - "type": "address" - } - ], - "name": "InviteClaimed", - "type": "event" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/PermissionedDisputeGame.json b/packages/contracts-bedrock/snapshots/abi/PermissionedDisputeGame.json index fd9737cc5842c..eebc4adf16ea5 100644 --- a/packages/contracts-bedrock/snapshots/abi/PermissionedDisputeGame.json +++ b/packages/contracts-bedrock/snapshots/abi/PermissionedDisputeGame.json @@ -2,54 +2,61 @@ { "inputs": [ { - "internalType": "GameType", - "name": "_gameType", - "type": "uint32" - }, - { - "internalType": "Claim", - "name": "_absolutePrestate", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_maxGameDepth", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_splitDepth", - "type": "uint256" - }, - { - "internalType": "Duration", - "name": "_clockExtension", - "type": "uint64" - }, - { - "internalType": "Duration", - "name": "_maxClockDuration", - "type": "uint64" - }, - { - "internalType": "contract IBigStepper", - "name": "_vm", - "type": "address" - }, - { - "internalType": "contract IDelayedWETH", - "name": "_weth", - "type": "address" - }, - { - "internalType": "contract IAnchorStateRegistry", - "name": "_anchorStateRegistry", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_l2ChainId", - "type": "uint256" + "components": [ + { + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "maxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "splitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "clockExtension", + "type": "uint64" + }, + { + "internalType": "Duration", + "name": "maxClockDuration", + "type": "uint64" + }, + { + "internalType": "contract IBigStepper", + "name": "vm", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "weth", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistry", + "type": "address" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + } + ], + "internalType": "struct FaultDisputeGame.GameConstructorParams", + "name": "_params", + "type": "tuple" }, { "internalType": "address", diff --git a/packages/contracts-bedrock/snapshots/abi/RISCV.json b/packages/contracts-bedrock/snapshots/abi/RISCV.json new file mode 100644 index 0000000000000..1650fd3980ec9 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/RISCV.json @@ -0,0 +1,68 @@ +[ + { + "inputs": [ + { + "internalType": "contract IPreimageOracle", + "name": "_oracle", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract IPreimageOracle", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_stateData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "_proof", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_localContext", + "type": "bytes32" + } + ], + "name": "step", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/Optimist.json b/packages/contracts-bedrock/snapshots/abi/SoulGasToken.json similarity index 61% rename from packages/contracts-bedrock/snapshots/abi/Optimist.json rename to packages/contracts-bedrock/snapshots/abi/SoulGasToken.json index 96bbc0591a30e..401a1e1bea053 100644 --- a/packages/contracts-bedrock/snapshots/abi/Optimist.json +++ b/packages/contracts-bedrock/snapshots/abi/SoulGasToken.json @@ -2,81 +2,73 @@ { "inputs": [ { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "string", - "name": "_symbol", - "type": "string" - }, - { - "internalType": "address", - "name": "_baseURIAttestor", - "type": "address" - }, - { - "internalType": "contract AttestationStation", - "name": "_attestationStation", - "type": "address" - }, - { - "internalType": "contract OptimistAllowlist", - "name": "_optimistAllowlist", - "type": "address" + "internalType": "bool", + "name": "_isBackedByNative", + "type": "bool" } ], "stateMutability": "nonpayable", - "type": "constructor" + "type": " +" }, { - "inputs": [], - "name": "ATTESTATION_STATION", - "outputs": [ + "inputs": [ { - "internalType": "contract AttestationStation", - "name": "", - "type": "address" + "internalType": "address[]", + "name": "_burners", + "type": "address[]" } ], - "stateMutability": "view", + "name": "addBurners", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "BASE_URI_ATTESTATION_KEY", - "outputs": [ + "inputs": [ { - "internalType": "bytes32", - "name": "", - "type": "bytes32" + "internalType": "address[]", + "name": "_minters", + "type": "address[]" } ], - "stateMutability": "view", + "name": "addMinters", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "BASE_URI_ATTESTOR", - "outputs": [ + "inputs": [ { - "internalType": "address", - "name": "", - "type": "address" + "internalType": "address[]", + "name": "_contracts", + "type": "address[]" } ], - "stateMutability": "view", + "name": "allowSgtValue", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "OPTIMIST_ALLOWLIST", + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", "outputs": [ { - "internalType": "contract OptimistAllowlist", + "internalType": "uint256", "name": "", - "type": "address" + "type": "uint256" } ], "stateMutability": "view", @@ -96,15 +88,21 @@ } ], "name": "approve", - "outputs": [], - "stateMutability": "pure", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "owner", + "name": "account", "type": "address" } ], @@ -120,64 +118,91 @@ "type": "function" }, { - "inputs": [], - "name": "baseURI", - "outputs": [ + "inputs": [ { - "internalType": "string", - "name": "uri_", - "type": "string" + "internalType": "address[]", + "name": "_accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_values", + "type": "uint256[]" } ], - "stateMutability": "view", + "name": "batchBurnFrom", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" + "internalType": "address[]", + "name": "_accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_values", + "type": "uint256[]" } ], - "name": "burn", + "name": "batchDepositFor", "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "payable", "type": "function" }, { "inputs": [ + { + "internalType": "address[]", + "name": "_accounts", + "type": "address[]" + }, { "internalType": "uint256", - "name": "tokenId", + "name": "_value", "type": "uint256" } ], - "name": "getApproved", - "outputs": [ + "name": "batchDepositForAll", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ { - "internalType": "address", - "name": "", - "type": "address" + "internalType": "address[]", + "name": "_accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_values", + "type": "uint256[]" } ], - "stateMutability": "view", + "name": "batchMint", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { - "internalType": "string", - "name": "_name", - "type": "string" + "internalType": "address[]", + "name": "_accounts", + "type": "address[]" }, { - "internalType": "string", - "name": "_symbol", - "type": "string" + "internalType": "uint256[]", + "name": "_values", + "type": "uint256[]" } ], - "name": "initialize", + "name": "batchWithdrawFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -186,21 +211,47 @@ "inputs": [ { "internalType": "address", - "name": "owner", + "name": "_account", "type": "address" }, { - "internalType": "address", - "name": "operator", - "type": "address" + "internalType": "uint256", + "name": "_value", + "type": "uint256" } ], - "name": "isApprovedForAll", + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "chargeFromOrigin", "outputs": [ { - "internalType": "bool", + "internalType": "uint256", + "name": "amountCharged_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", "name": "", - "type": "bool" + "type": "uint8" } ], "stateMutability": "view", @@ -210,154 +261,165 @@ "inputs": [ { "internalType": "address", - "name": "_recipient", + "name": "spender", "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" } ], - "name": "isOnAllowList", + "name": "decreaseAllowance", "outputs": [ { "internalType": "bool", - "name": "allowed_", + "name": "", "type": "bool" } ], - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { - "internalType": "address", - "name": "_recipient", - "type": "address" + "internalType": "address[]", + "name": "_burners", + "type": "address[]" } ], - "name": "mint", + "name": "delBurners", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "name", - "outputs": [ + "inputs": [ { - "internalType": "string", - "name": "", - "type": "string" + "internalType": "address[]", + "name": "_minters", + "type": "address[]" } ], - "stateMutability": "view", + "name": "delMinters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", "type": "function" }, { "inputs": [ { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" + "internalType": "address[]", + "name": "_contracts", + "type": "address[]" } ], - "stateMutability": "view", + "name": "disallowSgtValue", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", + "name": "spender", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "addedValue", "type": "uint256" } ], - "name": "safeTransferFrom", - "outputs": [], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" + "internalType": "string", + "name": "_name", + "type": "string" }, { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" + "internalType": "string", + "name": "_symbol", + "type": "string" }, { - "internalType": "bytes", - "name": "data", - "type": "bytes" + "internalType": "address", + "name": "_owner", + "type": "address" } ], - "name": "safeTransferFrom", + "name": "initialize", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, + "inputs": [], + "name": "isBackedByNative", + "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "name", + "outputs": [ { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" + "internalType": "string", + "name": "", + "type": "string" } ], - "name": "supportsInterface", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", "outputs": [ { - "internalType": "bool", + "internalType": "address", "name": "", - "type": "bool" + "type": "address" } ], "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "symbol", @@ -371,79 +433,115 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { "internalType": "address", - "name": "_owner", + "name": "", "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" } ], - "name": "tokenIdOfAddress", + "name": "transfer", "outputs": [ { - "internalType": "uint256", + "internalType": "bool", "name": "", - "type": "uint256" + "type": "bool" } ], - "stateMutability": "pure", + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, { "internalType": "uint256", - "name": "_tokenId", + "name": "", "type": "uint256" } ], - "name": "tokenURI", + "name": "transferFrom", "outputs": [ { - "internalType": "string", - "name": "uri_", - "type": "string" + "internalType": "bool", + "name": "", + "type": "bool" } ], - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "from", + "name": "newOwner", "type": "address" - }, + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", - "name": "to", + "name": "_account", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "_value", "type": "uint256" } ], - "name": "transferFrom", + "name": "withdrawFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "version", - "outputs": [ + "anonymous": false, + "inputs": [ { - "internalType": "string", - "name": "", - "type": "string" + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" } ], - "stateMutability": "view", - "type": "function" + "name": "AllowSgtValue", + "type": "event" }, { "anonymous": false, @@ -457,13 +555,13 @@ { "indexed": true, "internalType": "address", - "name": "approved", + "name": "spender", "type": "address" }, { - "indexed": true, + "indexed": false, "internalType": "uint256", - "name": "tokenId", + "name": "value", "type": "uint256" } ], @@ -476,23 +574,11 @@ { "indexed": true, "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", + "name": "from", "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" } ], - "name": "ApprovalForAll", + "name": "DisallowSgtValue", "type": "event" }, { @@ -508,6 +594,25 @@ "name": "Initialized", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -524,9 +629,9 @@ "type": "address" }, { - "indexed": true, + "indexed": false, "internalType": "uint256", - "name": "tokenId", + "name": "value", "type": "uint256" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/DelayedVetoable.json b/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json similarity index 52% rename from packages/contracts-bedrock/snapshots/abi/DelayedVetoable.json rename to packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json index d76d1c8b108b9..a75bb2ba7234b 100644 --- a/packages/contracts-bedrock/snapshots/abi/DelayedVetoable.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json @@ -3,85 +3,59 @@ "inputs": [ { "internalType": "address", - "name": "_vetoer", + "name": "_token", "type": "address" }, { "internalType": "address", - "name": "_initiator", + "name": "_from", "type": "address" }, { "internalType": "address", - "name": "_target", + "name": "_to", "type": "address" }, { "internalType": "uint256", - "name": "_operatingDelay", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "stateMutability": "nonpayable", - "type": "fallback" - }, - { - "inputs": [], - "name": "delay", - "outputs": [ - { - "internalType": "uint256", - "name": "delay_", + "name": "_amount", "type": "uint256" } ], + "name": "relayERC20", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "initiator", - "outputs": [ + "inputs": [ { "internalType": "address", - "name": "initiator_", + "name": "_token", "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + }, { - "internalType": "bytes32", - "name": "_callHash", - "type": "bytes32" - } - ], - "name": "queuedAt", - "outputs": [ + "internalType": "address", + "name": "_to", + "type": "address" + }, { "internalType": "uint256", - "name": "queuedAt_", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_chainId", "type": "uint256" } ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "target", + "name": "sendERC20", "outputs": [ { - "internalType": "address", - "name": "target_", - "type": "address" + "internalType": "bytes32", + "name": "msgHash_", + "type": "bytes32" } ], "stateMutability": "nonpayable", @@ -101,48 +75,40 @@ "type": "function" }, { - "inputs": [], - "name": "vetoer", - "outputs": [ + "anonymous": false, + "inputs": [ { + "indexed": true, "internalType": "address", - "name": "vetoer_", + "name": "token", "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, { "indexed": false, "internalType": "uint256", - "name": "delay", + "name": "amount", "type": "uint256" - } - ], - "name": "DelayActivated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "callHash", - "type": "bytes32" }, { "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" + "internalType": "uint256", + "name": "source", + "type": "uint256" } ], - "name": "Forwarded", + "name": "RelayERC20", "type": "event" }, { @@ -150,58 +116,56 @@ "inputs": [ { "indexed": true, - "internalType": "bytes32", - "name": "callHash", - "type": "bytes32" + "internalType": "address", + "name": "token", + "type": "address" }, { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "Initiated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, { "indexed": true, - "internalType": "bytes32", - "name": "callHash", - "type": "bytes32" + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" }, { "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" + "internalType": "uint256", + "name": "destination", + "type": "uint256" } ], - "name": "Vetoed", + "name": "SendERC20", "type": "event" }, { "inputs": [], - "name": "ForwardingEarly", + "name": "InvalidCrossDomainSender", "type": "error" }, { - "inputs": [ - { - "internalType": "address", - "name": "expected", - "type": "address" - }, - { - "internalType": "address", - "name": "actual", - "type": "address" - } - ], + "inputs": [], + "name": "InvalidERC7802", + "type": "error" + }, + { + "inputs": [], "name": "Unauthorized", "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json index 600e0e6b64f7b..e8df09a806406 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json @@ -11,12 +11,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -59,7 +59,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], @@ -74,6 +74,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "crosschainBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "crosschainMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "decimals", @@ -111,21 +147,21 @@ "inputs": [ { "internalType": "address", - "name": "from", + "name": "_from", "type": "address" }, { "internalType": "address", - "name": "dst", + "name": "_to", "type": "address" }, { "internalType": "uint256", - "name": "wad", + "name": "_amount", "type": "uint256" } ], - "name": "relayERC20", + "name": "relayETH", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -134,23 +170,43 @@ "inputs": [ { "internalType": "address", - "name": "dst", + "name": "_to", "type": "address" }, { "internalType": "uint256", - "name": "wad", + "name": "_chainId", "type": "uint256" - }, + } + ], + "name": "sendETH", + "outputs": [ { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" + "internalType": "bytes32", + "name": "msgHash_", + "type": "bytes32" } ], - "name": "sendERC20", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "_interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", "type": "function" }, { @@ -249,7 +305,7 @@ "inputs": [ { "internalType": "uint256", - "name": "wad", + "name": "_amount", "type": "uint256" } ], @@ -283,6 +339,56 @@ "name": "Approval", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "CrosschainBurn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "CrosschainMint", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -330,7 +436,7 @@ "type": "uint256" } ], - "name": "RelayERC20", + "name": "RelayETH", "type": "event" }, { @@ -361,7 +467,7 @@ "type": "uint256" } ], - "name": "SendERC20", + "name": "SendETH", "type": "event" }, { @@ -408,6 +514,11 @@ "name": "Withdrawal", "type": "event" }, + { + "inputs": [], + "name": "InvalidCrossDomainSender", + "type": "error" + }, { "inputs": [], "name": "NotCustomGasToken", @@ -417,5 +528,10 @@ "inputs": [], "name": "Unauthorized", "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json index 695231b9b1196..b7e18556fa2cb 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json @@ -199,6 +199,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "eip1559Denominator", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip1559Elasticity", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasLimit", @@ -586,6 +612,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_denominator", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "_elasticity", + "type": "uint32" + } + ], + "name": "setEIP1559Params", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json index 64f72945615b4..a459af15801b5 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json @@ -220,6 +220,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "eip1559Denominator", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip1559Elasticity", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasLimit", @@ -747,6 +773,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_denominator", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "_elasticity", + "type": "uint32" + } + ], + "name": "setEIP1559Params", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/WETH.json b/packages/contracts-bedrock/snapshots/abi/WETH.json index 9430e99a4c857..03cf2880ccce0 100644 --- a/packages/contracts-bedrock/snapshots/abi/WETH.json +++ b/packages/contracts-bedrock/snapshots/abi/WETH.json @@ -11,12 +11,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -59,7 +59,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/WETH98.json b/packages/contracts-bedrock/snapshots/abi/WETH98.json index 364d6b0591dc0..019b5c37a1af9 100644 --- a/packages/contracts-bedrock/snapshots/abi/WETH98.json +++ b/packages/contracts-bedrock/snapshots/abi/WETH98.json @@ -11,12 +11,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -59,7 +59,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi_loader.go b/packages/contracts-bedrock/snapshots/abi_loader.go index 34d6375436b7e..de84178e1a383 100644 --- a/packages/contracts-bedrock/snapshots/abi_loader.go +++ b/packages/contracts-bedrock/snapshots/abi_loader.go @@ -28,6 +28,9 @@ var systemConfig []byte //go:embed abi/CrossL2Inbox.json var crossL2Inbox []byte +//go:embed abi/SoulGasToken.json +var solGasToken []byte + func LoadDisputeGameFactoryABI() *abi.ABI { return loadABI(disputeGameFactory) } @@ -52,6 +55,10 @@ func LoadCrossL2InboxABI() *abi.ABI { return loadABI(crossL2Inbox) } +func LoadSoulGasTokenABI() *abi.ABI { + return loadABI(solGasToken) +} + func loadABI(json []byte) *abi.ABI { if parsed, err := abi.JSON(bytes.NewReader(json)); err != nil { panic(err) diff --git a/packages/contracts-bedrock/snapshots/abi_loader_test.go b/packages/contracts-bedrock/snapshots/abi_loader_test.go index 0bb58922c6c28..d48d818711a90 100644 --- a/packages/contracts-bedrock/snapshots/abi_loader_test.go +++ b/packages/contracts-bedrock/snapshots/abi_loader_test.go @@ -17,6 +17,7 @@ func TestLoadABIs(t *testing.T) { {"PreimageOracle", LoadPreimageOracleABI}, {"MIPS", LoadMIPSABI}, {"DelayedWETH", LoadDelayedWETHABI}, + {"SoulGasToken", LoadSoulGasTokenABI}, } for _, test := range tests { test := test diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json new file mode 100644 index 0000000000000..71894d8aa42a8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -0,0 +1,226 @@ +{ + "src/L1/DataAvailabilityChallenge.sol": { + "initCodeHash": "0x2d2efa7ac3e2f96a5712344c8df0ef4657c6fe430c4aa33e68dd8327d708b6b6", + "sourceCodeHash": "0x262e3e3f144fc93fd431296251961a70e939703bf62d1c79f23239644ac8e978" + }, + "src/L1/L1CrossDomainMessenger.sol": { + "initCodeHash": "0x9e8763b2fca99b577e8374a1f8d0421eeeb3e922535728a22d24a1b658c85665", + "sourceCodeHash": "0x370dd42d9888fd53a85811190208bc0f68e4071a2e3e4b2d15f2a4b92f4707ff" + }, + "src/L1/L1ERC721Bridge.sol": { + "initCodeHash": "0x7d8dbcc9b146d21ce4aabfc41a724d4c8d945956318a120127234afd53a4315c", + "sourceCodeHash": "0x48f4e1db42f82490e91fd33b05117b05c072444ed53bbd4e06e742d330a422bb" + }, + "src/L1/L1StandardBridge.sol": { + "initCodeHash": "0x9037e1930606d8cd092738bf28dda74e8f993aade26ae5a79a9e1fe2cb1d8513", + "sourceCodeHash": "0x5b3dc434f58afd7667f82b8fdfdbea488441be0bae8bdff15cfc5a42ce670cc1" + }, + "src/L1/L2OutputOracle.sol": { + "initCodeHash": "0x0e0573841e43e4c5f0df5839db2bdf387f29fed7c830f05960542e7d7109ed4a", + "sourceCodeHash": "0x8aafeffb41332fddf2fb1ef4fc033bd1f323cdc5b199c6951da73e3cb86276e6" + }, + "src/L1/OPContractsManager.sol": { + "initCodeHash": "0x320a6d4417eb0d2597b4c6f4caa37bbf9e35f38d3ad27ddb57f149c680e6afff", + "sourceCodeHash": "0x3a6ac40939df1d9f4c88caa4e6139454de5e0ad4a241b27d1bab65a3ae44610d" + }, + "src/L1/OptimismPortal.sol": { + "initCodeHash": "0xa8b2f8a6d1092c5e64529736462ebb35daa9ea9e67585f7de8e3e5394682ee64", + "sourceCodeHash": "0xb71e8bc24ea9ebb5692762005f2936ba2a00bf169e1e32f504a0f6e23a349a22" + }, + "src/L1/OptimismPortal2.sol": { + "initCodeHash": "0xa943efcc061bc59d129649de04ef8ba6318e2ff6eb10383b09ea71e3cbac5e5e", + "sourceCodeHash": "0x73df6c482332264954659ef4bcc18b7fb02a64a727018b4ae1aed8d2ec11c959" + }, + "src/L1/OptimismPortalInterop.sol": { + "initCodeHash": "0x7f8118c0abdcae94ebd08b15709b27bf7abe8fec96c74be109f2126d99f943a1", + "sourceCodeHash": "0x813fcf02c02798ebba8ed93f95eca82bdf9080c0edc5f2492c72c19b6c5f36b4" + }, + "src/L1/ProtocolVersions.sol": { + "initCodeHash": "0xb0ff1661226417342001fe9f0b64c340b7c074ff71579abf05399f4e742aaca1", + "sourceCodeHash": "0xc82754087747c067d4c3ae7deed08a574acbeaec0fbacc1f80ce63313ae817ed" + }, + "src/L1/SuperchainConfig.sol": { + "initCodeHash": "0xddfc0ea9a7d5b0a3a3c1080d022295af57cd9bcd6171ad0fe09287c493c9e95d", + "sourceCodeHash": "0xb9f372ce43c42179efd7d7ee7f7df29f89c484efb7cd6e83430b615d1c2592d8" + }, + "src/L1/SystemConfig.sol": { + "initCodeHash": "0x3ba55b46516de34186ff0cc92af9ca3ff916989ecb7d2fa9e82000f648607985", + "sourceCodeHash": "0x4085b02ea01cd16172a1809ddd9be69c567f7b204cefc93f7c4d9071da812daa" + }, + "src/L1/SystemConfigInterop.sol": { + "initCodeHash": "0xed198351099bd243a7a69e64944f43a3f203b5778ac55dbec428cc4df337cd8e", + "sourceCodeHash": "0x733fd71047569d974ac39477c6b6d55ec4100f32ac40b0597a0f7bdbde2867c3" + }, + "src/L2/BaseFeeVault.sol": { + "initCodeHash": "0x6745b7be3895a5e8d373df0066d931bae29c47672ac46c2f5829bd0052cc6d9e", + "sourceCodeHash": "0x45ea3acdbc8d1e583d4395239c9e9956e8cddda501f2e8eea50113333390f708" + }, + "src/L2/CrossL2Inbox.sol": { + "initCodeHash": "0x7a189f6dff6c19ec6f1e94d84a0d9d98a320a68812f957e50bf8b63224bb0dce", + "sourceCodeHash": "0x9bbfabb19b7f572dadae797786c2f87d892693650151bd8de6eadee3e03fc559" + }, + "src/L2/ETHLiquidity.sol": { + "initCodeHash": "0xbb16de6a3f678db7301694a000f315154f25f9660c8dcec4b0bef20bc7cfdebd", + "sourceCodeHash": "0x0576b189811bd343c2cdafcb512ece2c2ea20077bef8754a3dc3e3c80210b225" + }, + "src/L2/GasPriceOracle.sol": { + "initCodeHash": "0x83d50e3b34cd1b4de32f1cced28796b07aefc526cc17ceb1903ad55f4abc90b7", + "sourceCodeHash": "0x305c72d7be9149fce7095bd4641a1a19acada3126fbc43599f674cadbf6e7d6c" + }, + "src/L2/L1Block.sol": { + "initCodeHash": "0x33f35f0b23e1c76afa86a10eeb50e4a312ac2dda175202480de68c9d7c60abbe", + "sourceCodeHash": "0xc0d2de6a1c1b6799628e1db364cdc83ce296837dc3cc447349edd69b57146db2" + }, + "src/L2/L1BlockInterop.sol": { + "initCodeHash": "0xb96ef2536087b67b919d592f619999244652390825f79626f690bc1381093992", + "sourceCodeHash": "0x9493f90136917fc95d2ac942f061c1b9cffeff6d327afb46fe4e69784e7f2100" + }, + "src/L2/L1FeeVault.sol": { + "initCodeHash": "0x6745b7be3895a5e8d373df0066d931bae29c47672ac46c2f5829bd0052cc6d9e", + "sourceCodeHash": "0xd0471c328c1d17c5863261322bf8d5aff2e7e9e3a1135631a993aa75667621df" + }, + "src/L2/L2CrossDomainMessenger.sol": { + "initCodeHash": "0xcef22d29fed2e160e4c4350dee5c7671d2fd280895f9d2e4655b7060c56d5ba7", + "sourceCodeHash": "0x754c71bffa5525159bc1d60c0085a5edc23fba0384a44e8c2c1215eaa7fd17b7" + }, + "src/L2/L2ERC721Bridge.sol": { + "initCodeHash": "0xf30040071bb7def116eab4c26c921d6c7b24e4f02d001601f52a1ee5bd6629fc", + "sourceCodeHash": "0xc7258cb394333527bfc58d0a8ba6bd9d90b46b60ace20a500d94192fea60aef4" + }, + "src/L2/L2StandardBridge.sol": { + "initCodeHash": "0x7d2089948a373c62cdae80f329d6882eea450f2048f5ad45dbcceaaf37685d80", + "sourceCodeHash": "0xd64cf85ff5ecc1bf368d16532a9e29ba2dfc0f5456bbd3f9f05212c9120a2ed1" + }, + "src/L2/L2StandardBridgeInterop.sol": { + "initCodeHash": "0x024711e1689bedcdb1e9de8b1c4a440a911634a0ce0a384246b4f9cd683a23f3", + "sourceCodeHash": "0xb92682bb93a87c22e6a2506b64ff609b76376a7d09f514125173c6792c354fc4" + }, + "src/L2/L2ToL1MessagePasser.sol": { + "initCodeHash": "0xf9d82084dcef31a3737a76d8ee4e5842ea190d0f77ed4678adb3bbb95217050f", + "sourceCodeHash": "0xaef8ea36c5b78cd12e0e62811d51db627ccf0dfd2cc5479fb707a10ef0d42048" + }, + "src/L2/L2ToL2CrossDomainMessenger.sol": { + "initCodeHash": "0x45564b97c63419cc12eadc60425c6d001857a3eea688ecaf1439ae7ede6aa9aa", + "sourceCodeHash": "0xed64736338b43a42f6bc6a88cca734403e1bb9ceafa55e4738605dfdedd1a99f" + }, + "src/L2/OptimismSuperchainERC20.sol": { + "initCodeHash": "0xdac32a1057a6bc8a8d2ffdce1db8f34950cd0ffd1454d2133865736d21869192", + "sourceCodeHash": "0x4a7924f2195074145ac8e6221d77b24cd22d97423db2053937897e9d788990e2" + }, + "src/L2/OptimismSuperchainERC20Beacon.sol": { + "initCodeHash": "0x8a4d7cac6dd8ce583c996837893b93560297be1269f97f785a502748b25ba310", + "sourceCodeHash": "0xb57024e16b528bade5fee7c236e03ffbb3f22e6376e6852e2109298af850b43c" + }, + "src/L2/OptimismSuperchainERC20Factory.sol": { + "initCodeHash": "0x44659ea207ed173db4f1b519944c09c671d49f118e9d9ab85a010b8ebaf899e7", + "sourceCodeHash": "0xa1c0346cfe6932dde05dc6c1d9505cac38434d8a8f9e1e437253b1f4115f2506" + }, + "src/L2/SequencerFeeVault.sol": { + "initCodeHash": "0x02ca6cb6eebd2d6b91cf1eab483ee00b3233a7e8ad31f0e9cafc1f645ab3c24a", + "sourceCodeHash": "0x85c740c0888368ee95607635818ee698c27582e8917f40bc590d240447376da9" + }, + "src/L2/SuperchainERC20.sol": { + "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "sourceCodeHash": "0x981dca5b09da9038a9dff071b40a880e1b52b20268c6780ef54be3bc98a4f629" + }, + "src/L2/SuperchainTokenBridge.sol": { + "initCodeHash": "0x6b568ed564aede82a3a4cbcdb51282cad0e588a3fe6d91cf76616d3113df3901", + "sourceCodeHash": "0xcd2b49cb7cf6d18616ee8bec9183fe5b5b460941875bc0b4158c4d5390ec3b0c" + }, + "src/L2/SuperchainWETH.sol": { + "initCodeHash": "0x6ded8aeea6edf7e0ead7b0d2a12ef236f1fb7d21980a1dd564cbe86affca7927", + "sourceCodeHash": "0x11d711704a5afcae6076d017ee001b25bc705728973b1ad2e6a32274a8475f50" + }, + "src/L2/WETH.sol": { + "initCodeHash": "0x480d4f8dbec1b0d3211bccbbdfb69796f3e90c784f724b1bbfd4703b0aafdeba", + "sourceCodeHash": "0xe9964aa66db1dfc86772958b4c9276697e67f7055529a43e6a49a055009bc995" + }, + "src/cannon/MIPS.sol": { + "initCodeHash": "0xc10654f0e6498f424f7a5095bac36005dc7062d3813cc8f805a15005fc37406b", + "sourceCodeHash": "0x6c45dd23cb0d6f9bf4f84855ad0caf70e53dee3fe6c41454f7bf8df52ec3a9af" + }, + "src/cannon/MIPS2.sol": { + "initCodeHash": "0x7476695bb101cb45213793291124e3ec41e13a02d291837b76d8a35bfc8ec2c1", + "sourceCodeHash": "0xeaceb5d28bd58fca6a234d9291ca01424bf83576d191ee3046272bc4987d0b29" + }, + "src/cannon/MIPS64.sol": { + "initCodeHash": "0x6516160f35a85abb65d8102fa71f03cb57518787f9af85bc951f27ee60e6bb8f", + "sourceCodeHash": "0xd0802842e5656639f33324ed6498c60013e6d9cd63c9f097090da9a0a61700a4" + }, + "src/cannon/PreimageOracle.sol": { + "initCodeHash": "0xf08736a5af9277a4f3498dfee84a40c9b05f1a2ba3177459bebe2b0b54f99343", + "sourceCodeHash": "0x14b952b2a00bc4ec5e149bb5fb2a973bb255f0fd3f4a42b6bd05bc3bbe51f2b1" + }, + "src/dispute/AnchorStateRegistry.sol": { + "initCodeHash": "0x4e34a0d8de12cad21f37dc66755289ee38d1568a781f8ecc7ac38a0023d167ae", + "sourceCodeHash": "0xc9a8758bdd790ea3eff1a2d7416934d34a48b4a4e5340a66bd41044b27d36952" + }, + "src/dispute/DelayedWETH.sol": { + "initCodeHash": "0xee29b653713e6c33d263cc0c24b922b7defc08a4cab8e0ee77ca25a8139ed8dd", + "sourceCodeHash": "0xe6951dbc1d15c0e7dc0e88e25fe3a3d4511ac5b96a80091d5ec08e12004477af" + }, + "src/dispute/DisputeGameFactory.sol": { + "initCodeHash": "0x35d72c68d7e408ad3f8cd7fb94e695292ab64cc7b2563609c31b315bffb713f2", + "sourceCodeHash": "0x0b480d83f7eb65e9a9de6feff1474382504e4a32076c769f535081ed99f52acf" + }, + "src/dispute/FaultDisputeGame.sol": { + "initCodeHash": "0x423e8488731c0b0f87b435174f412c09fbf0b17eb0b8c9a03efa37d779ec0cae", + "sourceCodeHash": "0xe53b970922b309ada1c59f94d5935ffca669e909c797f17ba8a3d309c487e7e8" + }, + "src/legacy/DeployerWhitelist.sol": { + "initCodeHash": "0x53099379ed48b87f027d55712dbdd1da7d7099925426eb0531da9c0012e02c29", + "sourceCodeHash": "0xf22c94ed20c32a8ed2705a22d12c6969c3c3bad409c4efe2f95b0db74f210e10" + }, + "src/legacy/L1BlockNumber.sol": { + "initCodeHash": "0x60dded11d35e42fe15ef5dd94d28aae6b8ff3e67c6fbbc667a6729fcb3ca7a9a", + "sourceCodeHash": "0x53ef11021a52e9c87024a870566ec5dba1d1a12752396e654904384efdd8203e" + }, + "src/legacy/LegacyMessagePasser.sol": { + "initCodeHash": "0x3ca911b0578be7f8c91e7d01442a5609f04e5866768f99c8e31627c9ba79c9f0", + "sourceCodeHash": "0x62c9a6182d82692fb9c173ddb0d7978bcff2d1d4dc8cd2f10625e1e65bda6888" + }, + "src/safe/DeputyGuardianModule.sol": { + "initCodeHash": "0x5eaf823d81995ce1f703f26e31049c54c1d4902dd9873a0b4645d470f2f459a2", + "sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb" + }, + "src/safe/LivenessGuard.sol": { + "initCodeHash": "0xc8e29e8b12f423c8cd229a38bc731240dd815d96f1b0ab96c71494dde63f6a81", + "sourceCodeHash": "0x72b8d8d855e7af8beee29330f6cb9b9069acb32e23ce940002ec9a41aa012a16" + }, + "src/safe/LivenessModule.sol": { + "initCodeHash": "0xde3b3273aa37604048b5fa228b90f3b05997db613dfcda45061545a669b2476a", + "sourceCodeHash": "0x918965e52bbd358ac827ebe35998f5d8fa5ca77d8eb9ab8986b44181b9aaa48a" + }, + "src/universal/OptimismMintableERC20.sol": { + "initCodeHash": "0x51346d105fb0ebeb8733add0d53048b5d3d6f2d762c6bb446616c7f5da5eb026", + "sourceCodeHash": "0x876c716f9159909890eef3ffd3348a7f329af07e2899ae5ddc4a7809a3b753ce" + }, + "src/universal/OptimismMintableERC20Factory.sol": { + "initCodeHash": "0x080b30bbf556e8782dd2b355d5c9889324cf2ed1f9e6aca22a2e4dc42fab8ce7", + "sourceCodeHash": "0x47158ca0a9278bf367c4eebdb184aa31332f3b5a504f5147cf1c14777d9106ec" + }, + "src/universal/OptimismMintableERC721.sol": { + "initCodeHash": "0xa43e7ffce142c0f2ae6ebe22decdf146dd39246830bec5cbd7903b32c2599048", + "sourceCodeHash": "0x5222efbb8e5b650e0778687ea3b3ca8df16d1683c7180862c77fe146dd21ea79" + }, + "src/universal/OptimismMintableERC721Factory.sol": { + "initCodeHash": "0x8ff88eb88c3b756b51e2f011bdf4387992a4f48abb2ab0a38fe6bb50ffea3301", + "sourceCodeHash": "0x483e20d002a402034a0a5b2ff60c9e69afa4f0f9917d287dfe820b7bae08a7bb" + }, + "src/universal/StorageSetter.sol": { + "initCodeHash": "0x8831c079f7b7a52679e8a15e0ea14e30ea7bb4f93feed0fcd369942fe8c1f1ec", + "sourceCodeHash": "0x42151e2547ec5270353977fd66e78fa1fde18f362d7021cf7ddce16d5201b3ec" + }, + "src/vendor/asterisc/RISCV.sol": { + "initCodeHash": "0x7329cca924e189eeaa2d883234f6cb5fd787c8bf3339d8298e721778c2947ce5", + "sourceCodeHash": "0x02025b303a8f37b4e541f8c7936a8651402a60ea0147a53176e06b51b15a1f84" + }, + "src/vendor/eas/EAS.sol": { + "initCodeHash": "0xce1700cfc0a8e346b0a8e8c64b6570ba731d874b434b4798fe3176f3903c404b", + "sourceCodeHash": "0xde4c41139672fc0581ba77425ab1d822a8123ceaa3ad0655be869fcc722b8add" + }, + "src/vendor/eas/SchemaRegistry.sol": { + "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", + "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" + } +} \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/AttestationStation.json b/packages/contracts-bedrock/snapshots/storageLayout/AttestationStation.json deleted file mode 100644 index c3c732cec14d1..0000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/AttestationStation.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "bytes": "32", - "label": "attestations", - "offset": 0, - "slot": "0", - "type": "mapping(address => mapping(address => mapping(bytes32 => bytes)))" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/DelayedVetoable.json b/packages/contracts-bedrock/snapshots/storageLayout/DelayedVetoable.json deleted file mode 100644 index 7da3cbbe5bd66..0000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/DelayedVetoable.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "bytes": "32", - "label": "_delay", - "offset": 0, - "slot": "0", - "type": "uint256" - }, - { - "bytes": "32", - "label": "_queuedAt", - "offset": 0, - "slot": "1", - "type": "mapping(bytes32 => uint256)" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json b/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json index 4e8b771cd9bb3..6fdbd4718b690 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json @@ -36,14 +36,14 @@ }, { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "101", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "102", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json index 2928d2147b5c8..f18f01d5a780f 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json @@ -75,5 +75,12 @@ "offset": 0, "slot": "7", "type": "uint256" + }, + { + "bytes": "262144", + "label": "historyHashes", + "offset": 0, + "slot": "8", + "type": "bytes32[8192]" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json index 14ee2ff9609a0..c34e7ade7157a 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json @@ -76,11 +76,18 @@ "slot": "7", "type": "uint256" }, + { + "bytes": "262144", + "label": "historyHashes", + "offset": 0, + "slot": "8", + "type": "bytes32[8192]" + }, { "bytes": "64", "label": "dependencySet", "offset": 0, - "slot": "8", + "slot": "8200", "type": "struct EnumerableSet.UintSet" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimistAllowlist.json b/packages/contracts-bedrock/snapshots/storageLayout/MIPS64.json similarity index 100% rename from packages/contracts-bedrock/snapshots/storageLayout/OptimistAllowlist.json rename to packages/contracts-bedrock/snapshots/storageLayout/MIPS64.json diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json index aeef539c5c208..aa8148b34cba2 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json @@ -1,51 +1,30 @@ [ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, { "bytes": "32", - "label": "latestRelease", + "label": "l1ContractsRelease", "offset": 0, - "slot": "1", + "slot": "0", "type": "string" }, - { - "bytes": "32", - "label": "implementations", - "offset": 0, - "slot": "2", - "type": "mapping(string => mapping(string => struct OPContractsManager.Implementation))" - }, { "bytes": "32", "label": "systemConfigs", "offset": 0, - "slot": "3", + "slot": "1", "type": "mapping(uint256 => contract ISystemConfig)" }, { "bytes": "256", "label": "blueprint", "offset": 0, - "slot": "4", + "slot": "2", "type": "struct OPContractsManager.Blueprints" }, { - "bytes": "1600", - "label": "__gap", + "bytes": "288", + "label": "implementation", "offset": 0, - "slot": "12", - "type": "uint256[50]" + "slot": "10", + "type": "struct OPContractsManager.Implementations" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json index aeef539c5c208..aa8148b34cba2 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json @@ -1,51 +1,30 @@ [ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, { "bytes": "32", - "label": "latestRelease", + "label": "l1ContractsRelease", "offset": 0, - "slot": "1", + "slot": "0", "type": "string" }, - { - "bytes": "32", - "label": "implementations", - "offset": 0, - "slot": "2", - "type": "mapping(string => mapping(string => struct OPContractsManager.Implementation))" - }, { "bytes": "32", "label": "systemConfigs", "offset": 0, - "slot": "3", + "slot": "1", "type": "mapping(uint256 => contract ISystemConfig)" }, { "bytes": "256", "label": "blueprint", "offset": 0, - "slot": "4", + "slot": "2", "type": "struct OPContractsManager.Blueprints" }, { - "bytes": "1600", - "label": "__gap", + "bytes": "288", + "label": "implementation", "offset": 0, - "slot": "12", - "type": "uint256[50]" + "slot": "10", + "type": "struct OPContractsManager.Implementations" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimistInviter.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimistInviter.json deleted file mode 100644 index 5d1a6bbc43c91..0000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimistInviter.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, - { - "bytes": "32", - "label": "_HASHED_NAME", - "offset": 0, - "slot": "1", - "type": "bytes32" - }, - { - "bytes": "32", - "label": "_HASHED_VERSION", - "offset": 0, - "slot": "2", - "type": "bytes32" - }, - { - "bytes": "1600", - "label": "__gap", - "offset": 0, - "slot": "3", - "type": "uint256[50]" - }, - { - "bytes": "32", - "label": "commitmentTimestamps", - "offset": 0, - "slot": "53", - "type": "mapping(bytes32 => uint256)" - }, - { - "bytes": "32", - "label": "usedNonces", - "offset": 0, - "slot": "54", - "type": "mapping(address => mapping(bytes32 => bool))" - }, - { - "bytes": "32", - "label": "inviteCounts", - "offset": 0, - "slot": "55", - "type": "mapping(address => uint256)" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/RISCV.json b/packages/contracts-bedrock/snapshots/storageLayout/RISCV.json new file mode 100644 index 0000000000000..a79dc13a1d368 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/RISCV.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "20", + "label": "oracle", + "offset": 0, + "slot": "0", + "type": "contract IPreimageOracle" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/Optimist.json b/packages/contracts-bedrock/snapshots/storageLayout/SoulGasToken.json similarity index 61% rename from packages/contracts-bedrock/snapshots/storageLayout/Optimist.json rename to packages/contracts-bedrock/snapshots/storageLayout/SoulGasToken.json index 6049beb54245e..ce8662ad00f05 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/Optimist.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SoulGasToken.json @@ -21,66 +21,59 @@ "type": "uint256[50]" }, { - "bytes": "1600", - "label": "__gap", + "bytes": "32", + "label": "_balances", "offset": 0, "slot": "51", - "type": "uint256[50]" + "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "_name", + "label": "_allowances", "offset": 0, - "slot": "101", - "type": "string" + "slot": "52", + "type": "mapping(address => mapping(address => uint256))" }, { "bytes": "32", - "label": "_symbol", + "label": "_totalSupply", "offset": 0, - "slot": "102", - "type": "string" + "slot": "53", + "type": "uint256" }, { "bytes": "32", - "label": "_owners", - "offset": 0, - "slot": "103", - "type": "mapping(uint256 => address)" - }, - { - "bytes": "32", - "label": "_balances", + "label": "_name", "offset": 0, - "slot": "104", - "type": "mapping(address => uint256)" + "slot": "54", + "type": "string" }, { "bytes": "32", - "label": "_tokenApprovals", + "label": "_symbol", "offset": 0, - "slot": "105", - "type": "mapping(uint256 => address)" + "slot": "55", + "type": "string" }, { - "bytes": "32", - "label": "_operatorApprovals", + "bytes": "1440", + "label": "__gap", "offset": 0, - "slot": "106", - "type": "mapping(address => mapping(address => bool))" + "slot": "56", + "type": "uint256[45]" }, { - "bytes": "1408", - "label": "__gap", + "bytes": "20", + "label": "_owner", "offset": 0, - "slot": "107", - "type": "uint256[44]" + "slot": "101", + "type": "address" }, { - "bytes": "1600", + "bytes": "1568", "label": "__gap", "offset": 0, - "slot": "151", - "type": "uint256[50]" + "slot": "102", + "type": "uint256[49]" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainTokenBridge.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainTokenBridge.json new file mode 100644 index 0000000000000..0637a088a01e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainTokenBridge.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json index ac5f38a75a045..93eac8f284290 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json @@ -1,14 +1,14 @@ [ { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "0", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "1", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json index b0946e1bc4dba..a6184a1f10dd5 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json @@ -82,5 +82,19 @@ "offset": 0, "slot": "105", "type": "struct IResourceMetering.ResourceConfig" + }, + { + "bytes": "4", + "label": "eip1559Denominator", + "offset": 0, + "slot": "106", + "type": "uint32" + }, + { + "bytes": "4", + "label": "eip1559Elasticity", + "offset": 4, + "slot": "106", + "type": "uint32" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json index b0946e1bc4dba..a6184a1f10dd5 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json @@ -82,5 +82,19 @@ "offset": 0, "slot": "105", "type": "struct IResourceMetering.ResourceConfig" + }, + { + "bytes": "4", + "label": "eip1559Denominator", + "offset": 0, + "slot": "106", + "type": "uint32" + }, + { + "bytes": "4", + "label": "eip1559Elasticity", + "offset": 4, + "slot": "106", + "type": "uint32" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/WETH.json b/packages/contracts-bedrock/snapshots/storageLayout/WETH.json index ac5f38a75a045..93eac8f284290 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/WETH.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/WETH.json @@ -1,14 +1,14 @@ [ { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "0", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "1", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json b/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json index ac5f38a75a045..93eac8f284290 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json @@ -1,14 +1,14 @@ [ { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "0", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "1", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/src/L1/DataAvailabilityChallenge.sol b/packages/contracts-bedrock/src/L1/DataAvailabilityChallenge.sol index 2a725fc4f2002..634d6da91dd78 100644 --- a/packages/contracts-bedrock/src/L1/DataAvailabilityChallenge.sol +++ b/packages/contracts-bedrock/src/L1/DataAvailabilityChallenge.sol @@ -8,7 +8,7 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O import { SafeCall } from "src/libraries/SafeCall.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @dev An enum representing the status of a DA challenge. enum ChallengeStatus { @@ -24,9 +24,10 @@ enum CommitmentType { } /// @dev A struct representing a single DA challenge. -/// @custom:field status The status of the challenge. /// @custom:field challenger The address that initiated the challenge. +/// @custom:field lockedBond The amount of ETH bond that was locked by the challenger. /// @custom:field startBlock The block number at which the challenge was initiated. +/// @custom:field resolvedBlock The block number at which the challenge was resolved. struct Challenge { address challenger; uint256 lockedBond; @@ -94,8 +95,8 @@ contract DataAvailabilityChallenge is OwnableUpgradeable, ISemver { event BalanceChanged(address account, uint256 balance); /// @notice Semantic version. - /// @custom:semver 1.0.1-beta.2 - string public constant version = "1.0.1-beta.2"; + /// @custom:semver 1.0.1-beta.4 + string public constant version = "1.0.1-beta.4"; /// @notice The fixed cost of resolving a challenge. /// @dev The value is estimated by measuring the cost of resolving with `bytes(0)` diff --git a/packages/contracts-bedrock/src/L1/DelayedVetoable.sol b/packages/contracts-bedrock/src/L1/DelayedVetoable.sol deleted file mode 100644 index d968af2149753..0000000000000 --- a/packages/contracts-bedrock/src/L1/DelayedVetoable.sol +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; - -/// @title DelayedVetoable -/// @notice This contract enables a delay before a call is forwarded to a target contract, and during the delay period -/// the call can be vetoed by the authorized vetoer. -/// This contract does not support value transfers, only data is forwarded. -/// Additionally, this contract cannot be used to forward calls with data beginning with the function selector -/// of the queuedAt(bytes32) function. This is because of input validation checks which solidity performs at -/// runtime on functions which take an argument. -contract DelayedVetoable is ISemver { - /// @notice Error for when attempting to forward too early. - error ForwardingEarly(); - - /// @notice Error for unauthorized calls. - error Unauthorized(address expected, address actual); - - /// @notice An event that is emitted when the delay is activated. - /// @param delay The delay that was activated. - event DelayActivated(uint256 delay); - - /// @notice An event that is emitted when a call is initiated. - /// @param callHash The hash of the call data. - /// @param data The data of the initiated call. - event Initiated(bytes32 indexed callHash, bytes data); - - /// @notice An event that is emitted each time a call is forwarded. - /// @param callHash The hash of the call data. - /// @param data The data forwarded to the target. - event Forwarded(bytes32 indexed callHash, bytes data); - - /// @notice An event that is emitted each time a call is vetoed. - /// @param callHash The hash of the call data. - /// @param data The data forwarded to the target. - event Vetoed(bytes32 indexed callHash, bytes data); - - /// @notice The address that all calls are forwarded to after the delay. - address internal immutable TARGET; - - /// @notice The address that can veto a call. - address internal immutable VETOER; - - /// @notice The address that can initiate a call. - address internal immutable INITIATOR; - - /// @notice The delay which will be set after the initial system deployment is completed. - uint256 internal immutable OPERATING_DELAY; - - /// @notice The current amount of time to wait before forwarding a call. - uint256 internal _delay; - - /// @notice The time that a call was initiated. - mapping(bytes32 => uint256) internal _queuedAt; - - /// @notice A modifier that reverts if not called by the vetoer or by address(0) to allow - /// eth_call to interact with this proxy without needing to use low-level storage - /// inspection. We assume that nobody is able to trigger calls from address(0) during - /// normal EVM execution. - modifier readOrHandle() { - if (msg.sender == address(0)) { - _; - } else { - // This WILL halt the call frame on completion. - _handleCall(); - } - } - - /// @notice Semantic version. - /// @custom:semver 1.0.1-beta.2 - string public constant version = "1.0.1-beta.2"; - - /// @notice Sets the target admin during contract deployment. - /// @param _vetoer Address of the vetoer. - /// @param _initiator Address of the initiator. - /// @param _target Address of the target. - /// @param _operatingDelay Time to delay when the system is operational. - constructor(address _vetoer, address _initiator, address _target, uint256 _operatingDelay) { - // Note that the _delay value is not set here. Having an initial delay of 0 is helpful - // during the deployment of a new system. - VETOER = _vetoer; - INITIATOR = _initiator; - TARGET = _target; - OPERATING_DELAY = _operatingDelay; - } - - /// @notice Gets the initiator - /// @return initiator_ Initiator address. - function initiator() external virtual readOrHandle returns (address initiator_) { - initiator_ = INITIATOR; - } - - //// @notice Queries the vetoer address. - /// @return vetoer_ Vetoer address. - function vetoer() external virtual readOrHandle returns (address vetoer_) { - vetoer_ = VETOER; - } - - //// @notice Queries the target address. - /// @return target_ Target address. - function target() external readOrHandle returns (address target_) { - target_ = TARGET; - } - - /// @notice Gets the delay - /// @return delay_ Delay address. - function delay() external readOrHandle returns (uint256 delay_) { - delay_ = _delay; - } - - /// @notice Gets entries in the _queuedAt mapping. - /// @param _callHash The hash of the call data. - /// @return queuedAt_ The time the callHash was recorded. - function queuedAt(bytes32 _callHash) external readOrHandle returns (uint256 queuedAt_) { - queuedAt_ = _queuedAt[_callHash]; - } - - /// @notice Used for all calls that pass data to the contract. - fallback() external { - _handleCall(); - } - - /// @notice Receives all calls other than those made by the vetoer. - /// This enables transparent initiation and forwarding of calls to the target and avoids - /// the need for additional layers of abi encoding. - function _handleCall() internal { - // The initiator and vetoer activate the delay by passing in null data. - if (msg.data.length == 0 && _delay == 0) { - if (msg.sender != INITIATOR && msg.sender != VETOER) { - revert Unauthorized(INITIATOR, msg.sender); - } - _delay = OPERATING_DELAY; - emit DelayActivated(_delay); - return; - } - - bytes32 callHash = keccak256(msg.data); - - // Case 1: The initiator is calling the contract to initiate a call. - if (msg.sender == INITIATOR && _queuedAt[callHash] == 0) { - if (_delay == 0) { - // This forward function will halt the call frame on completion. - _forwardAndHalt(callHash); - } - _queuedAt[callHash] = block.timestamp; - emit Initiated(callHash, msg.data); - return; - } - - // Case 2: The vetoer is calling the contract to veto a call. - // Note: The vetoer retains the ability to veto even after the delay has passed. This makes censoring the vetoer - // more costly, as there is no time limit after which their transaction can be included. - if (msg.sender == VETOER && _queuedAt[callHash] != 0) { - delete _queuedAt[callHash]; - emit Vetoed(callHash, msg.data); - return; - } - - // Case 3: The call is from an unpermissioned actor. We'll forward the call if the delay has - // passed. - if (_queuedAt[callHash] == 0) { - // The call has not been initiated, so we'll treat this is an unauthorized initiation attempt. - revert Unauthorized(INITIATOR, msg.sender); - } - - if (_queuedAt[callHash] + _delay > block.timestamp) { - // Not enough time has passed, so we'll revert. - revert ForwardingEarly(); - } - - // Delete the call to prevent replays - delete _queuedAt[callHash]; - _forwardAndHalt(callHash); - } - - /// @notice Forwards the call to the target and halts the call frame. - function _forwardAndHalt(bytes32 _callHash) internal { - // Forward the call - emit Forwarded(_callHash, msg.data); - (bool success, bytes memory returndata) = TARGET.call(msg.data); - if (success == true) { - assembly { - return(add(returndata, 0x20), mload(returndata)) - } - } else { - assembly { - revert(add(returndata, 0x20), mload(returndata)) - } - } - } -} diff --git a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol index 27be4a7332fa2..071d35aea6a4c 100644 --- a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol @@ -8,10 +8,10 @@ import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; /// @custom:proxied true /// @title L1CrossDomainMessenger @@ -30,8 +30,8 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { ISystemConfig public systemConfig; /// @notice Semantic version. - /// @custom:semver 2.4.1-beta.2 - string public constant version = "2.4.1-beta.2"; + /// @custom:semver 2.4.1-beta.3 + string public constant version = "2.4.1-beta.3"; /// @notice Constructs the L1CrossDomainMessenger contract. constructor() CrossDomainMessenger() { diff --git a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol index dd01ff5cd2d31..b1bdc1e61cbeb 100644 --- a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol @@ -9,10 +9,10 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IL2ERC721Bridge } from "interfaces/L2/IL2ERC721Bridge.sol"; /// @custom:proxied true /// @title L1ERC721Bridge @@ -28,8 +28,8 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { ISuperchainConfig public superchainConfig; /// @notice Semantic version. - /// @custom:semver 2.1.1-beta.4 - string public constant version = "2.1.1-beta.4"; + /// @custom:semver 2.2.0-beta.2 + string public constant version = "2.2.0-beta.2"; /// @notice Constructs the L1ERC721Bridge contract. constructor() ERC721Bridge() { @@ -107,8 +107,8 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { require(_remoteToken != address(0), "L1ERC721Bridge: remote token cannot be address(0)"); // Construct calldata for _l2Token.finalizeBridgeERC721(_to, _tokenId) - bytes memory message = abi.encodeWithSelector( - IL2ERC721Bridge.finalizeBridgeERC721.selector, _remoteToken, _localToken, _from, _to, _tokenId, _extraData + bytes memory message = abi.encodeCall( + IL2ERC721Bridge.finalizeBridgeERC721, (_remoteToken, _localToken, _from, _to, _tokenId, _extraData) ); // Lock token into bridge diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index 6dd648f0d5410..a2747096717e4 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -8,10 +8,10 @@ import { StandardBridge } from "src/universal/StandardBridge.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; /// @custom:proxied true /// @title L1StandardBridge @@ -75,8 +75,8 @@ contract L1StandardBridge is StandardBridge, ISemver { ); /// @notice Semantic version. - /// @custom:semver 2.2.1-beta.1 - string public constant version = "2.2.1-beta.1"; + /// @custom:semver 2.2.1-beta.3 + string public constant version = "2.2.1-beta.3"; /// @notice Address of the SuperchainConfig contract. ISuperchainConfig public superchainConfig; diff --git a/packages/contracts-bedrock/src/L1/L2OutputOracle.sol b/packages/contracts-bedrock/src/L1/L2OutputOracle.sol index 99d5645dcecad..1af5bd8b7aa94 100644 --- a/packages/contracts-bedrock/src/L1/L2OutputOracle.sol +++ b/packages/contracts-bedrock/src/L1/L2OutputOracle.sol @@ -8,7 +8,7 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable import { Types } from "src/libraries/Types.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @custom:proxied true /// @title L2OutputOracle @@ -60,8 +60,8 @@ contract L2OutputOracle is Initializable, ISemver { event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex); /// @notice Semantic version. - /// @custom:semver 1.8.1-beta.2 - string public constant version = "1.8.1-beta.2"; + /// @custom:semver 1.8.1-beta.3 + string public constant version = "1.8.1-beta.3"; /// @notice Constructs the L2OutputOracle contract. Initializes variables to the same values as /// in the getting-started config. diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 4bf52ff228a19..aad48374704b0 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -1,41 +1,35 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Libraries import { Blueprint } from "src/libraries/Blueprint.sol"; import { Constants } from "src/libraries/Constants.sol"; - -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { ISystemConfigV160 } from "src/L1/interfaces/ISystemConfigV160.sol"; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; - -import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; - -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IPermissionedDisputeGame } from "src/dispute/interfaces/IPermissionedDisputeGame.sol"; import { Claim, Duration, GameType, GameTypes } from "src/dispute/lib/Types.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISystemConfigV160 } from "src/L1/interfaces/ISystemConfigV160.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; - -/// @custom:proxied true -contract OPContractsManager is ISemver, Initializable { +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; + +contract OPContractsManager is ISemver { // -------- Structs -------- /// @notice Represents the roles that can be set when deploying a standard OP Stack chain. @@ -89,19 +83,6 @@ contract OPContractsManager is ISemver, Initializable { IDelayedWETH delayedWETHPermissionlessGameProxy; } - /// @notice The logic address and initializer selector for an implementation contract. - struct Implementation { - address logic; // Address containing the deployed logic contract. - bytes4 initializer; // Function selector for the initializer. - } - - /// @notice Used to set the implementation for a contract by mapping a contract - /// name to the implementation data. - struct ImplementationSetter { - string name; // Contract name. - Implementation info; // Implementation to set. - } - /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size /// contracts, to reduce the code size of this factory contract. If it deployed full contracts /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would @@ -118,19 +99,23 @@ contract OPContractsManager is ISemver, Initializable { address permissionedDisputeGame2; } - /// @notice Inputs required when initializing the OPContractsManager. To avoid 'StackTooDeep' errors, - /// all necessary inputs (excluding immutables) for initialization are bundled together in this struct. - struct InitializerInputs { - Blueprints blueprints; - ImplementationSetter[] setters; - string release; - bool isLatest; + /// @notice The latest implementation contracts for the OP Stack. + struct Implementations { + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address delayedWETHImpl; + address mipsImpl; } // -------- Constants and Variables -------- - /// @custom:semver 1.0.0-beta.20 - string public constant version = "1.0.0-beta.20"; + /// @custom:semver 1.0.0-beta.23 + string public constant version = "1.0.0-beta.23"; /// @notice Represents the interface version so consumers know how to decode the DeployOutput struct /// that's emitted in the `Deployed` event. Whenever that struct changes, a new version should be used. @@ -142,24 +127,20 @@ contract OPContractsManager is ISemver, Initializable { /// @notice Address of the ProtocolVersions contract shared by all chains. IProtocolVersions public immutable protocolVersions; - /// @notice The latest release of the OP Contracts Manager, as a string of the format `op-contracts/vX.Y.Z`. - string public latestRelease; - - /// @notice Maps a release version to a contract name to it's implementation data. - mapping(string => mapping(string => Implementation)) public implementations; + // @notice L1 smart contracts release deployed by this version of OPCM. This is used in opcm to signal which version + // of the L1 smart contracts is deployed. It takes the format of `op-contracts/vX.Y.Z`. + string public l1ContractsRelease; /// @notice Maps an L2 Chain ID to the SystemConfig for that chain. mapping(uint256 => ISystemConfig) public systemConfigs; /// @notice Addresses of the Blueprint contracts. /// This is internal because if public the autogenerated getter method would return a tuple of - /// addresses, but we want it to return a struct. This is also set via `initialize` because - /// we can't make this an immutable variable as it is a non-value type. + /// addresses, but we want it to return a struct. Blueprints internal blueprint; - /// @notice Storage gap for future modifications, so we can expand the number of blueprints - /// without affecting other storage variables. - uint256[50] private __gap; + /// @notice Addresses of the latest implementation contracts. + Implementations internal implementation; // -------- Events -------- @@ -197,37 +178,26 @@ contract OPContractsManager is ISemver, Initializable { // -------- Methods -------- - /// @notice OPCM is proxied. Therefore the `initialize` function replaces most constructor logic for this contract. - - constructor(ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions) { + constructor( + ISuperchainConfig _superchainConfig, + IProtocolVersions _protocolVersions, + string memory _l1ContractsRelease, + Blueprints memory _blueprints, + Implementations memory _implementations + ) { assertValidContractAddress(address(_superchainConfig)); assertValidContractAddress(address(_protocolVersions)); superchainConfig = _superchainConfig; protocolVersions = _protocolVersions; - _disableInitializers(); - } - - function initialize(InitializerInputs memory _initializerInputs) public initializer { - if (_initializerInputs.isLatest) latestRelease = _initializerInputs.release; - if (keccak256(bytes(latestRelease)) == keccak256("")) revert LatestReleaseNotSet(); + l1ContractsRelease = _l1ContractsRelease; - for (uint256 i = 0; i < _initializerInputs.setters.length; i++) { - ImplementationSetter memory setter = _initializerInputs.setters[i]; - Implementation storage impl = implementations[_initializerInputs.release][setter.name]; - if (impl.logic != address(0)) revert AlreadyReleased(); - - impl.initializer = setter.info.initializer; - impl.logic = setter.info.logic; - } - - blueprint = _initializerInputs.blueprints; + blueprint = _blueprints; + implementation = _implementations; } function deploy(DeployInput calldata _input) external returns (DeployOutput memory) { assertValidInputs(_input); - uint256 l2ChainId = _input.l2ChainId; - // The salt for a non-proxy contract is a function of the chain ID and the salt mixer. string memory saltMixer = _input.saltMixer; bytes32 salt = keccak256(abi.encode(l2ChainId, saltMixer)); @@ -266,7 +236,6 @@ contract OPContractsManager is ISemver, Initializable { payable(Blueprint.deployFrom(blueprint.l1ChugSplashProxy, salt, abi.encode(output.opChainProxyAdmin))) ); output.opChainProxyAdmin.setProxyType(address(output.l1StandardBridgeProxy), IProxyAdmin.ProxyType.CHUGSPLASH); - string memory contractName = "OVM_L1CrossDomainMessenger"; output.l1CrossDomainMessengerProxy = IL1CrossDomainMessenger( Blueprint.deployFrom(blueprint.resolvedDelegateProxy, salt, abi.encode(output.addressManager, contractName)) @@ -275,10 +244,8 @@ contract OPContractsManager is ISemver, Initializable { address(output.l1CrossDomainMessengerProxy), IProxyAdmin.ProxyType.RESOLVED ); output.opChainProxyAdmin.setImplementationName(address(output.l1CrossDomainMessengerProxy), contractName); - // Now that all proxies are deployed, we can transfer ownership of the AddressManager to the ProxyAdmin. output.addressManager.transferOwnership(address(output.opChainProxyAdmin)); - // The AnchorStateRegistry Implementation is not MCP Ready, and therefore requires an implementation per chain. // It must be deployed after the DisputeGameFactoryProxy so that it can be provided as a constructor argument. output.anchorStateRegistryImpl = IAnchorStateRegistry( @@ -301,54 +268,76 @@ contract OPContractsManager is ISemver, Initializable { ); // -------- Set and Initialize Proxy Implementations -------- - Implementation memory impl; bytes memory data; - impl = getLatestImplementation("L1ERC721Bridge"); - data = encodeL1ERC721BridgeInitializer(impl.initializer, output); - upgradeAndCall(output.opChainProxyAdmin, address(output.l1ERC721BridgeProxy), impl.logic, data); + data = encodeL1ERC721BridgeInitializer(IL1ERC721Bridge.initialize.selector, output); + upgradeAndCall( + output.opChainProxyAdmin, address(output.l1ERC721BridgeProxy), implementation.l1ERC721BridgeImpl, data + ); - impl = getLatestImplementation("OptimismPortal"); - data = encodeOptimismPortalInitializer(impl.initializer, output); - upgradeAndCall(output.opChainProxyAdmin, address(output.optimismPortalProxy), impl.logic, data); + data = encodeOptimismPortalInitializer(IOptimismPortal2.initialize.selector, output); + upgradeAndCall( + output.opChainProxyAdmin, address(output.optimismPortalProxy), implementation.optimismPortalImpl, data + ); // First we upgrade the implementation so it's version can be retrieved, then we initialize // it afterwards. See the comments in encodeSystemConfigInitializer to learn more. - impl = getLatestImplementation("SystemConfig"); - output.opChainProxyAdmin.upgrade(payable(address(output.systemConfigProxy)), impl.logic); - data = encodeSystemConfigInitializer(impl.initializer, _input, output); - upgradeAndCall(output.opChainProxyAdmin, address(output.systemConfigProxy), impl.logic, data); + output.opChainProxyAdmin.upgrade(payable(address(output.systemConfigProxy)), implementation.systemConfigImpl); + data = encodeSystemConfigInitializer(_input, output); + upgradeAndCall( + output.opChainProxyAdmin, address(output.systemConfigProxy), implementation.systemConfigImpl, data + ); - impl = getLatestImplementation("OptimismMintableERC20Factory"); - data = encodeOptimismMintableERC20FactoryInitializer(impl.initializer, output); - upgradeAndCall(output.opChainProxyAdmin, address(output.optimismMintableERC20FactoryProxy), impl.logic, data); + data = encodeOptimismMintableERC20FactoryInitializer(IOptimismMintableERC20Factory.initialize.selector, output); + upgradeAndCall( + output.opChainProxyAdmin, + address(output.optimismMintableERC20FactoryProxy), + implementation.optimismMintableERC20FactoryImpl, + data + ); - impl = getLatestImplementation("L1CrossDomainMessenger"); - data = encodeL1CrossDomainMessengerInitializer(impl.initializer, output); - upgradeAndCall(output.opChainProxyAdmin, address(output.l1CrossDomainMessengerProxy), impl.logic, data); + data = encodeL1CrossDomainMessengerInitializer(IL1CrossDomainMessenger.initialize.selector, output); + upgradeAndCall( + output.opChainProxyAdmin, + address(output.l1CrossDomainMessengerProxy), + implementation.l1CrossDomainMessengerImpl, + data + ); - impl = getLatestImplementation("L1StandardBridge"); - data = encodeL1StandardBridgeInitializer(impl.initializer, output); - upgradeAndCall(output.opChainProxyAdmin, address(output.l1StandardBridgeProxy), impl.logic, data); + data = encodeL1StandardBridgeInitializer(IL1StandardBridge.initialize.selector, output); + upgradeAndCall( + output.opChainProxyAdmin, address(output.l1StandardBridgeProxy), implementation.l1StandardBridgeImpl, data + ); - impl = getLatestImplementation("DelayedWETH"); - data = encodeDelayedWETHInitializer(impl.initializer, _input); + data = encodeDelayedWETHInitializer(IDelayedWETH.initialize.selector, _input); // Eventually we will switch from DelayedWETHPermissionedGameProxy to DelayedWETHPermissionlessGameProxy. - upgradeAndCall(output.opChainProxyAdmin, address(output.delayedWETHPermissionedGameProxy), impl.logic, data); + upgradeAndCall( + output.opChainProxyAdmin, + address(output.delayedWETHPermissionedGameProxy), + implementation.delayedWETHImpl, + data + ); // We set the initial owner to this contract, set game implementations, then transfer ownership. - impl = getLatestImplementation("DisputeGameFactory"); - data = encodeDisputeGameFactoryInitializer(impl.initializer, _input); - upgradeAndCall(output.opChainProxyAdmin, address(output.disputeGameFactoryProxy), impl.logic, data); + data = encodeDisputeGameFactoryInitializer(IDisputeGameFactory.initialize.selector, _input); + upgradeAndCall( + output.opChainProxyAdmin, + address(output.disputeGameFactoryProxy), + implementation.disputeGameFactoryImpl, + data + ); output.disputeGameFactoryProxy.setImplementation( GameTypes.PERMISSIONED_CANNON, IDisputeGame(address(output.permissionedDisputeGame)) ); output.disputeGameFactoryProxy.transferOwnership(address(_input.roles.opChainProxyAdminOwner)); - impl.logic = address(output.anchorStateRegistryImpl); - impl.initializer = IAnchorStateRegistry.initialize.selector; - data = encodeAnchorStateRegistryInitializer(impl.initializer, _input); - upgradeAndCall(output.opChainProxyAdmin, address(output.anchorStateRegistryProxy), impl.logic, data); + data = encodeAnchorStateRegistryInitializer(IAnchorStateRegistry.initialize.selector, _input); + upgradeAndCall( + output.opChainProxyAdmin, + address(output.anchorStateRegistryProxy), + address(output.anchorStateRegistryImpl), + data + ); // -------- Finalize Deployment -------- // Transfer ownership of the ProxyAdmin from this contract to the specified owner. @@ -402,13 +391,6 @@ contract OPContractsManager is ISemver, Initializable { return Blueprint.deployFrom(blueprint.proxy, salt, abi.encode(_proxyAdmin)); } - /// @notice Returns the implementation data for a contract name. Makes a copy of the internal - // Implementation struct in storage to prevent accidental mutation of the internal data. - function getLatestImplementation(string memory _name) internal view returns (Implementation memory) { - Implementation storage impl = implementations[latestRelease][_name]; - return Implementation({ logic: impl.logic, initializer: impl.initializer }); - } - // -------- Initializer Encoding -------- /// @notice Helper method for encoding the L1ERC721Bridge initializer data. @@ -445,7 +427,6 @@ contract OPContractsManager is ISemver, Initializable { /// @notice Helper method for encoding the SystemConfig initializer data. function encodeSystemConfigInitializer( - bytes4 _selector, DeployInput memory _input, DeployOutput memory _output ) @@ -454,50 +435,22 @@ contract OPContractsManager is ISemver, Initializable { virtual returns (bytes memory) { - // We inspect the SystemConfig contract and determine it's signature here. This is required - // because this OPCM contract is being developed in a repository that no longer contains the - // SystemConfig contract that was released as part of `op-contracts/v1.6.0`, but in production - // it needs to support that version, in addition to the version currently on develop. - string memory semver = _output.systemConfigProxy.version(); - if (keccak256(abi.encode(semver)) == keccak256(abi.encode(string("2.2.0")))) { - // We are using the op-contracts/v1.6.0 SystemConfig contract. - ( - IResourceMetering.ResourceConfig memory referenceResourceConfig, - ISystemConfigV160.Addresses memory opChainAddrs - ) = defaultSystemConfigV160Params(_selector, _input, _output); - - return abi.encodeWithSelector( - _selector, - _input.roles.systemConfigOwner, - _input.basefeeScalar, - _input.blobBasefeeScalar, - bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash - _input.gasLimit, - _input.roles.unsafeBlockSigner, - referenceResourceConfig, - chainIdToBatchInboxAddress(_input.l2ChainId), - opChainAddrs - ); - } else { - // We are using the latest SystemConfig contract from the repo. - ( - IResourceMetering.ResourceConfig memory referenceResourceConfig, - ISystemConfig.Addresses memory opChainAddrs - ) = defaultSystemConfigParams(_selector, _input, _output); - - return abi.encodeWithSelector( - _selector, - _input.roles.systemConfigOwner, - _input.basefeeScalar, - _input.blobBasefeeScalar, - bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash - _input.gasLimit, - _input.roles.unsafeBlockSigner, - referenceResourceConfig, - chainIdToBatchInboxAddress(_input.l2ChainId), - opChainAddrs - ); - } + bytes4 selector = ISystemConfig.initialize.selector; + (IResourceMetering.ResourceConfig memory referenceResourceConfig, ISystemConfig.Addresses memory opChainAddrs) = + defaultSystemConfigParams(selector, _input, _output); + + return abi.encodeWithSelector( + selector, + _input.roles.systemConfigOwner, + _input.basefeeScalar, + _input.blobBasefeeScalar, + bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash + _input.gasLimit, + _input.roles.unsafeBlockSigner, + referenceResourceConfig, + chainIdToBatchInboxAddress(_input.l2ChainId), + opChainAddrs + ); } /// @notice Helper method for encoding the OptimismMintableERC20Factory initializer data. @@ -599,7 +552,7 @@ contract OPContractsManager is ISemver, Initializable { _input.disputeSplitDepth, _input.disputeClockExtension, _input.disputeMaxClockDuration, - IBigStepper(getLatestImplementation("MIPS").logic), + IBigStepper(implementation.mipsImpl), IDelayedWETH(payable(address(_output.delayedWETHPermissionedGameProxy))), IAnchorStateRegistry(address(_output.anchorStateRegistryProxy)), _input.l2ChainId, @@ -645,45 +598,6 @@ contract OPContractsManager is ISemver, Initializable { assertValidContractAddress(opChainAddrs_.optimismMintableERC20Factory); } - /// @notice Returns default, standard config arguments for the SystemConfig initializer. - /// This is used by subclasses to reduce code duplication. - function defaultSystemConfigV160Params( - bytes4, /* selector */ - DeployInput memory, /* _input */ - DeployOutput memory _output - ) - internal - view - virtual - returns ( - IResourceMetering.ResourceConfig memory resourceConfig_, - ISystemConfigV160.Addresses memory opChainAddrs_ - ) - { - // We use assembly to easily convert from IResourceMetering.ResourceConfig to ResourceMetering.ResourceConfig. - // This is required because we have not yet fully migrated the codebase to be interface-based. - IResourceMetering.ResourceConfig memory resourceConfig = Constants.DEFAULT_RESOURCE_CONFIG(); - assembly ("memory-safe") { - resourceConfig_ := resourceConfig - } - - opChainAddrs_ = ISystemConfigV160.Addresses({ - l1CrossDomainMessenger: address(_output.l1CrossDomainMessengerProxy), - l1ERC721Bridge: address(_output.l1ERC721BridgeProxy), - l1StandardBridge: address(_output.l1StandardBridgeProxy), - disputeGameFactory: address(_output.disputeGameFactoryProxy), - optimismPortal: address(_output.optimismPortalProxy), - optimismMintableERC20Factory: address(_output.optimismMintableERC20FactoryProxy) - }); - - assertValidContractAddress(opChainAddrs_.l1CrossDomainMessenger); - assertValidContractAddress(opChainAddrs_.l1ERC721Bridge); - assertValidContractAddress(opChainAddrs_.l1StandardBridge); - assertValidContractAddress(opChainAddrs_.disputeGameFactory); - assertValidContractAddress(opChainAddrs_.optimismPortal); - assertValidContractAddress(opChainAddrs_.optimismMintableERC20Factory); - } - /// @notice Makes an external call to the target to initialize the proxy with the specified data. /// First performs safety checks to ensure the target, implementation, and proxy admin are valid. function upgradeAndCall( @@ -710,4 +624,9 @@ contract OPContractsManager is ISemver, Initializable { function blueprints() public view returns (Blueprints memory) { return blueprint; } + + /// @notice Returns the implementation contract addresses. + function implementations() public view returns (Implementations memory) { + return implementation; + } } diff --git a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol b/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol index 9d541434a3974..b40ea48b2019c 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol @@ -1,25 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { OPContractsManager } from "src/L1/OPContractsManager.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -/// @custom:proxied true +// Interfaces +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; + contract OPContractsManagerInterop is OPContractsManager { constructor( ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions + IProtocolVersions _protocolVersions, + string memory _l1ContractsRelease, + Blueprints memory _blueprints, + Implementations memory _implementations ) - OPContractsManager(_superchainConfig, _protocolVersions) + OPContractsManager(_superchainConfig, _protocolVersions, _l1ContractsRelease, _blueprints, _implementations) { } // The `SystemConfigInterop` contract has an extra `address _dependencyManager` argument // that we must account for. function encodeSystemConfigInitializer( - bytes4 _selector, DeployInput memory _input, DeployOutput memory _output ) @@ -29,18 +34,19 @@ contract OPContractsManagerInterop is OPContractsManager { override returns (bytes memory) { + bytes4 selector = ISystemConfigInterop.initialize.selector; (IResourceMetering.ResourceConfig memory referenceResourceConfig, ISystemConfig.Addresses memory opChainAddrs) = - defaultSystemConfigParams(_selector, _input, _output); + defaultSystemConfigParams(selector, _input, _output); - // TODO For now we assume that the dependency manager is the same as the proxy admin owner. + // TODO For now we assume that the dependency manager is the same as system config owner. // This is currently undefined since it's not part of the standard config, so we may need // to update where this value is pulled from in the future. To support a different dependency // manager in this contract without an invasive change of redefining the `Roles` struct, // we will make the change described in https://github.com/ethereum-optimism/optimism/issues/11783. - address dependencyManager = address(_input.roles.opChainProxyAdminOwner); + address dependencyManager = address(_input.roles.systemConfigOwner); return abi.encodeWithSelector( - _selector, + selector, _input.roles.systemConfigOwner, _input.basefeeScalar, _input.blobBasefeeScalar, diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal.sol b/packages/contracts-bedrock/src/L1/OptimismPortal.sol index 885f375742127..6137558991ead 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal.sol @@ -14,16 +14,28 @@ import { Hashing } from "src/libraries/Hashing.sol"; import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; -import "src/libraries/PortalErrors.sol"; +import { + BadTarget, + LargeCalldata, + SmallGasLimit, + TransferFailed, + OnlyCustomGasToken, + NoValue, + Unauthorized, + CallPaused, + GasEstimation, + NonReentrant, + Unproven +} from "src/libraries/PortalErrors.sol"; // Interfaces import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @custom:proxied true /// @title OptimismPortal @@ -134,9 +146,9 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { } /// @notice Semantic version. - /// @custom:semver 2.8.1-beta.3 + /// @custom:semver 2.8.1-beta.5 function version() public pure virtual returns (string memory) { - return "2.8.1-beta.3"; + return "2.8.1-beta.5"; } /// @notice Constructs the OptimismPortal contract. diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 3a75269a2020d..494ad7a67c546 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -14,18 +14,37 @@ import { Hashing } from "src/libraries/Hashing.sol"; import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; -import "src/libraries/PortalErrors.sol"; -import "src/dispute/lib/Types.sol"; +import { + BadTarget, + LargeCalldata, + SmallGasLimit, + TransferFailed, + OnlyCustomGasToken, + NoValue, + Unauthorized, + CallPaused, + GasEstimation, + NonReentrant, + InvalidProof, + InvalidGameType, + InvalidDisputeGame, + InvalidMerkleProof, + Blacklisted, + Unproven, + ProposalNotValidated, + AlreadyFinalized +} from "src/libraries/PortalErrors.sol"; +import { GameStatus, GameType, Claim, Timestamp, Hash } from "src/dispute/lib/Types.sol"; // Interfaces import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @custom:proxied true /// @title OptimismPortal2 @@ -164,9 +183,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Semantic version. - /// @custom:semver 3.11.0-beta.5 + /// @custom:semver 3.11.0-beta.7 function version() public pure virtual returns (string memory) { - return "3.11.0-beta.5"; + return "3.11.0-beta.7"; } /// @notice Constructs the OptimismPortal contract. diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol index b02248eaff432..7c21244bf5a18 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -3,12 +3,14 @@ pragma solidity 0.8.15; // Contracts import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; -import { L1BlockInterop, ConfigType } from "src/L2/L1BlockInterop.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; -import "src/libraries/PortalErrors.sol"; +import { Unauthorized } from "src/libraries/PortalErrors.sol"; + +// Interfaces +import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// @custom:proxied true /// @title OptimismPortalInterop @@ -23,9 +25,9 @@ contract OptimismPortalInterop is OptimismPortal2 { OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) { } - /// @custom:semver +interop-beta.1 + /// @custom:semver +interop-beta.4 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.1"); + return string.concat(super.version(), "+interop-beta.4"); } /// @notice Sets static configuration options for the L2 system. @@ -48,7 +50,7 @@ contract OptimismPortalInterop is OptimismPortal2 { uint256(0), // value uint64(SYSTEM_DEPOSIT_GAS_LIMIT), // gasLimit false, // isCreation, - abi.encodeCall(L1BlockInterop.setConfig, (_type, _value)) + abi.encodeCall(IL1BlockInterop.setConfig, (_type, _value)) ) ); } diff --git a/packages/contracts-bedrock/src/L1/ProtocolVersions.sol b/packages/contracts-bedrock/src/L1/ProtocolVersions.sol index 8d4982ecea0c6..2253e38e35fee 100644 --- a/packages/contracts-bedrock/src/L1/ProtocolVersions.sol +++ b/packages/contracts-bedrock/src/L1/ProtocolVersions.sol @@ -1,10 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Libraries import { Storage } from "src/libraries/Storage.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + /// @notice ProtocolVersion is a numeric identifier of the protocol version. type ProtocolVersion is uint256; @@ -36,8 +41,8 @@ contract ProtocolVersions is OwnableUpgradeable, ISemver { event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); /// @notice Semantic version. - /// @custom:semver 1.0.1-beta.3 - string public constant version = "1.0.1-beta.3"; + /// @custom:semver 1.0.1-beta.5 + string public constant version = "1.0.1-beta.5"; /// @notice Constructs the ProtocolVersion contract. Cannot set /// the owner to `address(0)` due to the Ownable contract's diff --git a/packages/contracts-bedrock/src/L1/SuperchainConfig.sol b/packages/contracts-bedrock/src/L1/SuperchainConfig.sol index 51b13936c81bc..cceedbfd9320d 100644 --- a/packages/contracts-bedrock/src/L1/SuperchainConfig.sol +++ b/packages/contracts-bedrock/src/L1/SuperchainConfig.sol @@ -1,10 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Libraries import { Storage } from "src/libraries/Storage.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + /// @custom:proxied true /// @custom:audit none This contracts is not yet audited. /// @title SuperchainConfig @@ -36,8 +41,8 @@ contract SuperchainConfig is Initializable, ISemver { event ConfigUpdate(UpdateType indexed updateType, bytes data); /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; + /// @custom:semver 1.1.1-beta.3 + string public constant version = "1.1.1-beta.3"; /// @notice Constructs the SuperchainConfig contract. constructor() { diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index 032cb3227984f..e268c4be4f51c 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -11,9 +11,9 @@ import { Constants } from "src/libraries/Constants.sol"; import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; /// @custom:proxied true /// @title SystemConfig @@ -23,15 +23,16 @@ import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { /// @notice Enum representing different types of updates. /// @custom:value BATCHER Represents an update to the batcher hash. - /// @custom:value GAS_CONFIG Represents an update to txn fee config on L2. + /// @custom:value FEE_SCALARS Represents an update to l1 data fee scalars. /// @custom:value GAS_LIMIT Represents an update to gas limit on L2. /// @custom:value UNSAFE_BLOCK_SIGNER Represents an update to the signer key for unsafe /// block distrubution. enum UpdateType { BATCHER, - GAS_CONFIG, + FEE_SCALARS, GAS_LIMIT, - UNSAFE_BLOCK_SIGNER + UNSAFE_BLOCK_SIGNER, + EIP_1559_PARAMS } /// @notice Struct representing the addresses of L1 system contracts. These should be the @@ -123,6 +124,12 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { /// Set as internal with a getter so that the struct is returned instead of a tuple. IResourceMetering.ResourceConfig internal _resourceConfig; + /// @notice The EIP-1559 base fee max change denominator. + uint32 public eip1559Denominator; + + /// @notice The EIP-1559 elasticity multiplier. + uint32 public eip1559Elasticity; + /// @notice Emitted when configuration is updated. /// @param version SystemConfig version. /// @param updateType Type of update. @@ -130,9 +137,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); /// @notice Semantic version. - /// @custom:semver 2.3.0-beta.3 + /// @custom:semver 2.3.0-beta.7 function version() public pure virtual returns (string memory) { - return "2.3.0-beta.3"; + return "2.3.0-beta.7"; } /// @notice Constructs the SystemConfig contract. Cannot set @@ -217,7 +224,6 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { _setGasPayingToken(_addresses.gasPayingToken); _setResourceConfig(_config); - require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low"); } /// @notice Returns the minimum L2 gas limit that can be safely set for the system to @@ -380,7 +386,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { scalar = _scalar; bytes memory data = abi.encode(_overhead, _scalar); - emit ConfigUpdate(VERSION, UpdateType.GAS_CONFIG, data); + emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data); } /// @notice Updates gas config as of the Ecotone upgrade. Can only be called by the owner. @@ -400,7 +406,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { scalar = (uint256(0x01) << 248) | (uint256(_blobbasefeeScalar) << 32) | _basefeeScalar; bytes memory data = abi.encode(overhead, scalar); - emit ConfigUpdate(VERSION, UpdateType.GAS_CONFIG, data); + emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data); } /// @notice Updates the L2 gas limit. Can only be called by the owner. @@ -420,6 +426,25 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data); } + /// @notice Updates the EIP-1559 parameters of the chain. Can only be called by the owner. + /// @param _denominator EIP-1559 base fee max change denominator. + /// @param _elasticity EIP-1559 elasticity multiplier. + function setEIP1559Params(uint32 _denominator, uint32 _elasticity) external onlyOwner { + _setEIP1559Params(_denominator, _elasticity); + } + + /// @notice Internal function for updating the EIP-1559 parameters. + function _setEIP1559Params(uint32 _denominator, uint32 _elasticity) internal { + // require the parameters have sane values: + require(_denominator >= 1, "SystemConfig: denominator must be >= 1"); + require(_elasticity >= 1, "SystemConfig: elasticity must be >= 1"); + eip1559Denominator = _denominator; + eip1559Elasticity = _elasticity; + + bytes memory data = abi.encode(uint256(_denominator) << 32 | uint64(_elasticity)); + emit ConfigUpdate(VERSION, UpdateType.EIP_1559_PARAMS, data); + } + /// @notice Sets the start block in a backwards compatible way. Proxies /// that were initialized before the startBlock existed in storage /// can have their start block set by a user provided override. diff --git a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol index f7b8921d10d2d..ba8aaa5182226 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol @@ -3,9 +3,7 @@ pragma solidity 0.8.15; // Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IOptimismPortalInterop as IOptimismPortal } from "src/L1/interfaces/IOptimismPortalInterop.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; -import { ConfigType } from "src/L2/L1BlockInterop.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; @@ -14,7 +12,9 @@ import { StaticConfig } from "src/libraries/StaticConfig.sol"; import { Storage } from "src/libraries/Storage.sol"; // Interfaces -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +import { IOptimismPortalInterop as IOptimismPortal } from "interfaces/L1/IOptimismPortalInterop.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// @custom:proxied true /// @title SystemConfigInterop @@ -68,9 +68,9 @@ contract SystemConfigInterop is SystemConfig { Storage.setAddress(DEPENDENCY_MANAGER_SLOT, _dependencyManager); } - /// @custom:semver +interop-beta.1 + /// @custom:semver +interop-beta.6 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.1"); + return string.concat(super.version(), "+interop-beta.6"); } /// @notice Internal setter for the gas paying token address, includes validation. diff --git a/packages/contracts-bedrock/src/L1/interfaces/IDelayedVetoable.sol b/packages/contracts-bedrock/src/L1/interfaces/IDelayedVetoable.sol deleted file mode 100644 index 53fd168127639..0000000000000 --- a/packages/contracts-bedrock/src/L1/interfaces/IDelayedVetoable.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IDelayedVetoable { - error ForwardingEarly(); - error Unauthorized(address expected, address actual); - - event DelayActivated(uint256 delay); - event Forwarded(bytes32 indexed callHash, bytes data); - event Initiated(bytes32 indexed callHash, bytes data); - event Vetoed(bytes32 indexed callHash, bytes data); - - fallback() external; - - function delay() external returns (uint256 delay_); - function initiator() external returns (address initiator_); - function queuedAt(bytes32 _callHash) external returns (uint256 queuedAt_); - function target() external returns (address target_); - function version() external view returns (string memory); - function vetoer() external returns (address vetoer_); - - function __constructor__(address _vetoer, address _initiator, address _target, uint256 _operatingDelay) external; -} diff --git a/packages/contracts-bedrock/src/L1/interfaces/ISystemConfigV160.sol b/packages/contracts-bedrock/src/L1/interfaces/ISystemConfigV160.sol deleted file mode 100644 index deb0dd2c52ad6..0000000000000 --- a/packages/contracts-bedrock/src/L1/interfaces/ISystemConfigV160.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; - -/// @notice This interface corresponds to the op-contracts/v1.6.0 release of the SystemConfig -/// contract, which has a semver of 2.2.0 as specified in -/// https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 -interface ISystemConfigV160 { - enum UpdateType { - BATCHER, - GAS_CONFIG, - GAS_LIMIT, - UNSAFE_BLOCK_SIGNER - } - - struct Addresses { - address l1CrossDomainMessenger; - address l1ERC721Bridge; - address l1StandardBridge; - address disputeGameFactory; - address optimismPortal; - address optimismMintableERC20Factory; - } - - event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); - event Initialized(uint8 version); - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - function BATCH_INBOX_SLOT() external view returns (bytes32); - function DISPUTE_GAME_FACTORY_SLOT() external view returns (bytes32); - function L1_CROSS_DOMAIN_MESSENGER_SLOT() external view returns (bytes32); - function L1_ERC_721_BRIDGE_SLOT() external view returns (bytes32); - function L1_STANDARD_BRIDGE_SLOT() external view returns (bytes32); - function OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT() external view returns (bytes32); - function OPTIMISM_PORTAL_SLOT() external view returns (bytes32); - function START_BLOCK_SLOT() external view returns (bytes32); - function UNSAFE_BLOCK_SIGNER_SLOT() external view returns (bytes32); - function VERSION() external view returns (uint256); - function basefeeScalar() external view returns (uint32); - function batchInbox() external view returns (address addr_); - function batcherHash() external view returns (bytes32); - function blobbasefeeScalar() external view returns (uint32); - function disputeGameFactory() external view returns (address addr_); - function gasLimit() external view returns (uint64); - function gasPayingToken() external view returns (address addr_, uint8 decimals_); - function gasPayingTokenName() external view returns (string memory name_); - function gasPayingTokenSymbol() external view returns (string memory symbol_); - function initialize( - address _owner, - uint256 _basefeeScalar, - uint256 _blobbasefeeScalar, - bytes32 _batcherHash, - uint64 _gasLimit, - address _unsafeBlockSigner, - IResourceMetering.ResourceConfig memory _config, - address _batchInbox, - Addresses memory _addresses - ) - external; - function isCustomGasToken() external view returns (bool); - function l1CrossDomainMessenger() external view returns (address addr_); - function l1ERC721Bridge() external view returns (address addr_); - function l1StandardBridge() external view returns (address addr_); - function maximumGasLimit() external pure returns (uint64); - function minimumGasLimit() external view returns (uint64); - function optimismMintableERC20Factory() external view returns (address addr_); - function optimismPortal() external view returns (address addr_); - function overhead() external view returns (uint256); - function owner() external view returns (address); - function renounceOwnership() external; - function resourceConfig() external view returns (IResourceMetering.ResourceConfig memory); - function scalar() external view returns (uint256); - function setBatcherHash(bytes32 _batcherHash) external; - function setGasConfig(uint256 _overhead, uint256 _scalar) external; - function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external; - function setGasLimit(uint64 _gasLimit) external; - function setUnsafeBlockSigner(address _unsafeBlockSigner) external; - function startBlock() external view returns (uint256 startBlock_); - function transferOwnership(address newOwner) external; // nosemgrep - function unsafeBlockSigner() external view returns (address addr_); - function version() external pure returns (string memory); - - function __constructor__() external; -} diff --git a/packages/contracts-bedrock/src/L2/BaseFeeVault.sol b/packages/contracts-bedrock/src/L2/BaseFeeVault.sol index 2fd33b9290bfe..6f4e67b9d538b 100644 --- a/packages/contracts-bedrock/src/L2/BaseFeeVault.sol +++ b/packages/contracts-bedrock/src/L2/BaseFeeVault.sol @@ -1,19 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Contracts import { FeeVault } from "src/L2/FeeVault.sol"; +// Libraries import { Types } from "src/libraries/Types.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000019 /// @title BaseFeeVault /// @notice The BaseFeeVault accumulates the base fee that is paid by transactions. contract BaseFeeVault is FeeVault, ISemver { /// @notice Semantic version. - /// @custom:semver 1.5.0-beta.3 - string public constant version = "1.5.0-beta.3"; + /// @custom:semver 1.5.0-beta.5 + string public constant version = "1.5.0-beta.5"; /// @notice Constructs the BaseFeeVault contract. /// @param _recipient Wallet that will receive the fees. diff --git a/packages/contracts-bedrock/src/L2/CrossDomainOwnable.sol b/packages/contracts-bedrock/src/L2/CrossDomainOwnable.sol index 3b532f5890016..658cfc50f8b52 100644 --- a/packages/contracts-bedrock/src/L2/CrossDomainOwnable.sol +++ b/packages/contracts-bedrock/src/L2/CrossDomainOwnable.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Contracts import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +// Libraries import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; /// @title CrossDomainOwnable diff --git a/packages/contracts-bedrock/src/L2/CrossDomainOwnable2.sol b/packages/contracts-bedrock/src/L2/CrossDomainOwnable2.sol index 0711daffce3e3..491921066b65f 100644 --- a/packages/contracts-bedrock/src/L2/CrossDomainOwnable2.sol +++ b/packages/contracts-bedrock/src/L2/CrossDomainOwnable2.sol @@ -8,7 +8,7 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IL2CrossDomainMessenger } from "src/L2/interfaces/IL2CrossDomainMessenger.sol"; +import { IL2CrossDomainMessenger } from "interfaces/L2/IL2CrossDomainMessenger.sol"; /// @title CrossDomainOwnable2 /// @notice This contract extends the OpenZeppelin `Ownable` contract for L2 contracts to be owned diff --git a/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol b/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol index e940a026df05e..e5661a94ed884 100644 --- a/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol +++ b/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol @@ -8,7 +8,7 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IL2CrossDomainMessenger } from "src/L2/interfaces/IL2CrossDomainMessenger.sol"; +import { IL2CrossDomainMessenger } from "interfaces/L2/IL2CrossDomainMessenger.sol"; /// @title CrossDomainOwnable3 /// @notice This contract extends the OpenZeppelin `Ownable` contract for L2 contracts to be owned diff --git a/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol b/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol index 7939dccddbb40..4762940bd1c70 100644 --- a/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol +++ b/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol @@ -1,13 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; +// Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { TransientContext, TransientReentrancyAware } from "src/libraries/TransientContext.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ICrossL2Inbox } from "src/L2/interfaces/ICrossL2Inbox.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; -import { IDependencySet } from "src/L2/interfaces/IDependencySet.sol"; -import { IL1BlockInterop } from "src/L2/interfaces/IL1BlockInterop.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IDependencySet } from "interfaces/L2/IDependencySet.sol"; +import { IL1BlockInterop } from "interfaces/L2/IL1BlockInterop.sol"; /// @notice Thrown when the caller is not DEPOSITOR_ACCOUNT when calling `setInteropStart()` error NotDepositor(); @@ -30,12 +32,21 @@ error TargetCallFailed(); /// @notice Thrown when trying to execute a cross chain message on a deposit transaction. error NoExecutingDeposits(); +/// @notice The struct for a pointer to a message payload in a remote (or local) chain. +struct Identifier { + address origin; + uint256 blockNumber; + uint256 logIndex; + uint256 timestamp; + uint256 chainId; +} + /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000022 /// @title CrossL2Inbox /// @notice The CrossL2Inbox is responsible for executing a cross chain message on the destination /// chain. It is permissionless to execute a cross chain message on behalf of any user. -contract CrossL2Inbox is ICrossL2Inbox, ISemver, TransientReentrancyAware { +contract CrossL2Inbox is ISemver, TransientReentrancyAware { /// @notice Storage slot that the interop start timestamp is stored at. /// Equal to bytes32(uint256(keccak256("crossl2inbox.interopstart")) - 1) bytes32 internal constant INTEROP_START_SLOT = 0x5c769ee0ee8887661922049dc52480bb60322d765161507707dd9b190af5c149; @@ -65,8 +76,8 @@ contract CrossL2Inbox is ICrossL2Inbox, ISemver, TransientReentrancyAware { address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.8 - string public constant version = "1.0.0-beta.8"; + /// @custom:semver 1.0.0-beta.11 + string public constant version = "1.0.0-beta.11"; /// @notice Emitted when a cross chain message is being executed. /// @param msgHash Hash of message payload being executed. diff --git a/packages/contracts-bedrock/src/L2/ETHLiquidity.sol b/packages/contracts-bedrock/src/L2/ETHLiquidity.sol index 6118288df77df..8962f791c26c1 100644 --- a/packages/contracts-bedrock/src/L2/ETHLiquidity.sol +++ b/packages/contracts-bedrock/src/L2/ETHLiquidity.sol @@ -9,8 +9,8 @@ import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErro import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @title ETHLiquidity /// @notice The ETHLiquidity contract allows other contracts to access ETH liquidity without @@ -23,8 +23,8 @@ contract ETHLiquidity is ISemver { event LiquidityMinted(address indexed caller, uint256 value); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.3 - string public constant version = "1.0.0-beta.3"; + /// @custom:semver 1.0.0-beta.4 + string public constant version = "1.0.0-beta.4"; /// @notice Allows an address to lock ETH liquidity into this contract. function burn() external payable { diff --git a/packages/contracts-bedrock/src/L2/FeeVault.sol b/packages/contracts-bedrock/src/L2/FeeVault.sol index 856985d7827be..86c94fc3f6dca 100644 --- a/packages/contracts-bedrock/src/L2/FeeVault.sol +++ b/packages/contracts-bedrock/src/L2/FeeVault.sol @@ -6,7 +6,7 @@ import { SafeCall } from "src/libraries/SafeCall.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol"; +import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; // Libraries import { Types } from "src/libraries/Types.sol"; diff --git a/packages/contracts-bedrock/src/L2/GasPriceOracle.sol b/packages/contracts-bedrock/src/L2/GasPriceOracle.sol index 862e4bb079138..11b6c897db857 100644 --- a/packages/contracts-bedrock/src/L2/GasPriceOracle.sol +++ b/packages/contracts-bedrock/src/L2/GasPriceOracle.sol @@ -7,8 +7,8 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @custom:proxied true /// @custom:predeploy 0x420000000000000000000000000000000000000F @@ -29,8 +29,8 @@ contract GasPriceOracle is ISemver { uint256 public constant DECIMALS = 6; /// @notice Semantic version. - /// @custom:semver 1.3.1-beta.2 - string public constant version = "1.3.1-beta.2"; + /// @custom:semver 1.3.1-beta.4 + string public constant version = "1.3.1-beta.4"; /// @notice This is the intercept value for the linear regression used to estimate the final size of the /// compressed transaction. @@ -74,7 +74,7 @@ contract GasPriceOracle is ISemver { // Add 68 to the size to account for unsigned tx: uint256 txSize = _unsignedTxSize + 68; - // txSize / 255 + 16 is the pratical fastlz upper-bound covers %99.99 txs. + // txSize / 255 + 16 is the practical fastlz upper-bound covers %99.99 txs. uint256 flzUpperBound = txSize + txSize / 255 + 16; return _fjordL1Cost(flzUpperBound); diff --git a/packages/contracts-bedrock/src/L2/L1Block.sol b/packages/contracts-bedrock/src/L2/L1Block.sol index c61f45b836290..9557808bb1e64 100644 --- a/packages/contracts-bedrock/src/L2/L1Block.sol +++ b/packages/contracts-bedrock/src/L2/L1Block.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Libraries import { Constants } from "src/libraries/Constants.sol"; import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol"; -import "src/libraries/L1BlockErrors.sol"; +import { NotDepositor } from "src/libraries/L1BlockErrors.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000015 @@ -57,9 +60,9 @@ contract L1Block is ISemver, IGasToken { /// @notice The latest L1 blob base fee. uint256 public blobBaseFee; - /// @custom:semver 1.5.1-beta.2 + /// @custom:semver 1.5.1-beta.5 function version() public pure virtual returns (string memory) { - return "1.5.1-beta.2"; + return "1.5.1-beta.5"; } /// @notice Returns the gas paying token, its decimals, name and symbol. @@ -87,6 +90,11 @@ contract L1Block is ISemver, IGasToken { return token != Constants.ETHER; } + /// @notice size of historyHashes. + uint256 internal constant HISTORY_SIZE = 8192; + /// @notice The 8191 history L1 blockhashes and 1 latest L1 blockhash. + bytes32[HISTORY_SIZE] internal historyHashes; + /// @custom:legacy /// @notice Updates the L1 block values. /// @param _number L1 blocknumber. @@ -166,6 +174,30 @@ contract L1Block is ISemver, IGasToken { sstore(hash.slot, calldataload(100)) // bytes32 sstore(batcherHash.slot, calldataload(132)) // bytes32 } + + historyHashes[number % HISTORY_SIZE] = hash; + } + + /// @custom:legacy + /// @notice Returns the L1 block hash at the requested L1 blocknumber. + /// Only the most recent 8191 L1 block hashes are available, excluding the current one. + /// @param _historyNumber L1 blocknumber. + function blockHash(uint256 _historyNumber) external view returns (bytes32) { + // translated from + // [opBlockhash](https://github.com/ethereum/go-ethereum/blob/e31709db6570e302557a9bccd681034ea0dcc246/core/vm/instructions.go#L434) + // with 256 => HISTORY_SIZE-1 + uint256 lower; + uint256 upper = number; + if (upper < HISTORY_SIZE) { + lower = 0; + } else { + lower = upper - HISTORY_SIZE + 1; + } + if (_historyNumber >= lower && _historyNumber < upper) { + return historyHashes[_historyNumber % HISTORY_SIZE]; + } else { + return bytes32(0); + } } /// @notice Sets the gas paying token for the L2 system. Can only be called by the special @@ -178,4 +210,9 @@ contract L1Block is ISemver, IGasToken { emit GasPayingTokenSet({ token: _token, decimals: _decimals, name: _name, symbol: _symbol }); } + + /// @notice Returns the size of history hashes. + function historySize() external pure returns (uint256) { + return HISTORY_SIZE; + } } diff --git a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol index 15ea67f5e6b39..7b92b202a052d 100644 --- a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol +++ b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol @@ -9,7 +9,14 @@ import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableS import { GasPayingToken } from "src/libraries/GasPayingToken.sol"; import { StaticConfig } from "src/libraries/StaticConfig.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import "src/libraries/L1BlockErrors.sol"; +import { + NotDepositor, + NotCrossL2Inbox, + NotDependency, + DependencySetSizeTooLarge, + AlreadyDependency, + CantRemovedDependency +} from "src/libraries/L1BlockErrors.sol"; /// @notice Enum representing different types of configurations that can be set on L1BlockInterop. /// @custom:value SET_GAS_PAYING_TOKEN Represents the config type for setting the gas paying token. @@ -42,9 +49,9 @@ contract L1BlockInterop is L1Block { /// keccak256(abi.encode(uint256(keccak256("l1Block.identifier.isDeposit")) - 1)) & ~bytes32(uint256(0xff)) uint256 internal constant IS_DEPOSIT_SLOT = 0x921bd3a089295c6e5540e8fba8195448d253efd6f2e3e495b499b627dc36a300; - /// @custom:semver +interop + /// @custom:semver +interop-beta.3 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop"); + return string.concat(super.version(), "+interop-beta.3"); } /// @notice Returns whether the call was triggered from a a deposit or not. diff --git a/packages/contracts-bedrock/src/L2/L1FeeVault.sol b/packages/contracts-bedrock/src/L2/L1FeeVault.sol index c80c40b98493b..1d8a2520c1846 100644 --- a/packages/contracts-bedrock/src/L2/L1FeeVault.sol +++ b/packages/contracts-bedrock/src/L2/L1FeeVault.sol @@ -1,19 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Contracts import { FeeVault } from "src/L2/FeeVault.sol"; +// Libraries import { Types } from "src/libraries/Types.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + /// @custom:proxied true /// @custom:predeploy 0x420000000000000000000000000000000000001A /// @title L1FeeVault /// @notice The L1FeeVault accumulates the L1 portion of the transaction fees. contract L1FeeVault is FeeVault, ISemver { /// @notice Semantic version. - /// @custom:semver 1.5.0-beta.3 - string public constant version = "1.5.0-beta.3"; + /// @custom:semver 1.5.0-beta.5 + string public constant version = "1.5.0-beta.5"; /// @notice Constructs the L1FeeVault contract. /// @param _recipient Wallet that will receive the fees. diff --git a/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol index 2461e46d2cf5b..668b1756f5a20 100644 --- a/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol @@ -9,9 +9,9 @@ import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000007 @@ -20,8 +20,8 @@ import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; /// L2 on the L2 side. Users are generally encouraged to use this contract instead of lower /// level message passing contracts. contract L2CrossDomainMessenger is CrossDomainMessenger, ISemver { - /// @custom:semver 2.1.1-beta.4 - string public constant version = "2.1.1-beta.4"; + /// @custom:semver 2.1.1-beta.5 + string public constant version = "2.1.1-beta.5"; /// @notice Constructs the L2CrossDomainMessenger contract. constructor() CrossDomainMessenger() { diff --git a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol index 950f25203981d..c774c3ad4389a 100644 --- a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol @@ -9,10 +9,10 @@ import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC16 import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IOptimismMintableERC721 } from "src/universal/interfaces/IOptimismMintableERC721.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IOptimismMintableERC721 } from "interfaces/universal/IOptimismMintableERC721.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000014 @@ -26,8 +26,8 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// wait for the one-week challenge period to elapse before their Optimism-native NFT /// can be refunded on L2. contract L2ERC721Bridge is ERC721Bridge, ISemver { - /// @custom:semver 1.7.1-beta.3 - string public constant version = "1.7.1-beta.3"; + /// @custom:semver 1.8.0-beta.3 + string public constant version = "1.8.0-beta.3"; /// @notice Constructs the L2ERC721Bridge contract. constructor() ERC721Bridge() { @@ -117,8 +117,8 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver { // slither-disable-next-line reentrancy-events IOptimismMintableERC721(_localToken).burn(_from, _tokenId); - bytes memory message = abi.encodeWithSelector( - IL1ERC721Bridge.finalizeBridgeERC721.selector, remoteToken, _localToken, _from, _to, _tokenId, _extraData + bytes memory message = abi.encodeCall( + IL1ERC721Bridge.finalizeBridgeERC721, (remoteToken, _localToken, _from, _to, _tokenId, _extraData) ); // Send message to L1 bridge diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 1c7e2e307cc36..02fac4ee1dda9 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -3,15 +3,15 @@ pragma solidity 0.8.15; // Contracts import { StandardBridge } from "src/universal/StandardBridge.sol"; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000010 @@ -58,9 +58,9 @@ contract L2StandardBridge is StandardBridge, ISemver { ); /// @notice Semantic version. - /// @custom:semver 1.11.1-beta.2 + /// @custom:semver 1.11.1-beta.5 function version() public pure virtual returns (string memory) { - return "1.11.1-beta.2"; + return "1.11.1-beta.5"; } /// @notice Constructs the L2StandardBridge contract. diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index be6c9e4c878d6..be7323e0953d1 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -10,7 +10,8 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IOptimismERC20Factory } from "interfaces/L2/IOptimismERC20Factory.sol"; +import { IMintableAndBurnableERC20 } from "interfaces/L2/IMintableAndBurnableERC20.sol"; /// @notice Thrown when the decimals of the tokens are not the same. error InvalidDecimals(); @@ -18,26 +19,18 @@ error InvalidDecimals(); /// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory. error InvalidLegacyERC20Address(); -/// @notice Thrown when the SuperchainERC20 address is not found in the SuperchainERC20Factory. +/// @notice Thrown when the OptimismSuperchainERC20 address is not found in the OptimismSuperchainERC20Factory. error InvalidSuperchainERC20Address(); /// @notice Thrown when the remote addresses of the tokens are not the same. error InvalidTokenPair(); -/// TODO: Define a better naming convention for this interface. -/// @notice Interface for minting and burning tokens in the L2StandardBridge. -/// Used for StandardL2ERC20, OptimismMintableERC20 and OptimismSuperchainERC20. -interface MintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} - /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000010 /// @title L2StandardBridgeInterop /// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for /// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20) -/// and SuperchainERC20 tokens. +/// and OptimismSuperchainERC20 tokens. contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Emitted when a conversion is made. /// @param from The token being converted from. @@ -47,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); /// @notice Semantic version. - /// @custom:semver +interop + /// @custom:semver +interop-beta.4 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop"); + return string.concat(super.version(), "+interop-beta.4"); } /// @notice Converts `amount` of `from` token to `to` token. @@ -59,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge { function convert(address _from, address _to, uint256 _amount) external { _validatePair(_from, _to); - MintableAndBurnable(_from).burn(msg.sender, _amount); - MintableAndBurnable(_to).mint(msg.sender, _amount); + IMintableAndBurnableERC20(_from).burn(msg.sender, _amount); + IMintableAndBurnableERC20(_to).mint(msg.sender, _amount); emit Converted(_from, _to, msg.sender, _amount); } @@ -82,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Validates that the tokens are deployed by the correct factory. /// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20). - /// @param _superAddr The SuperchainERC20 address. + /// @param _superAddr The OptimismSuperchainERC20 address. function _validateFactories(address _legacyAddr, address _superAddr) internal view { // 2. Valid legacy check address _legacyRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr); if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address(); - // 3. Valid SuperchainERC20 check + // 3. Valid OptimismSuperchainERC20 check address _superRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr); if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address(); diff --git a/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol b/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol index 94b8213983e13..b17bda73c583f 100644 --- a/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol +++ b/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Libraries import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Encoding } from "src/libraries/Encoding.sol"; import { Burn } from "src/libraries/Burn.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000016 @@ -48,8 +51,8 @@ contract L2ToL1MessagePasser is ISemver { /// @param amount Amount of ETh that was burned. event WithdrawerBalanceBurnt(uint256 indexed amount); - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; + /// @custom:semver 1.1.1-beta.3 + string public constant version = "1.1.1-beta.3"; /// @notice Allows users to withdraw ETH by sending directly to this contract. receive() external payable { diff --git a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol index 4c1ffc38760fa..2162638ddc43a 100644 --- a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol @@ -1,16 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; +// Libraries import { Encoding } from "src/libraries/Encoding.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { CrossL2Inbox } from "src/L2/CrossL2Inbox.sol"; -import { ICrossL2Inbox } from "src/L2/interfaces/ICrossL2Inbox.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; import { TransientReentrancyAware } from "src/libraries/TransientContext.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IDependencySet } from "interfaces/L2/IDependencySet.sol"; +import { ICrossL2Inbox, Identifier } from "interfaces/L2/ICrossL2Inbox.sol"; + /// @notice Thrown when a non-written slot in transient storage is attempted to be read from. error NotEntered(); @@ -38,13 +40,19 @@ error MessageAlreadyRelayed(); /// @notice Thrown when a reentrant call is detected. error ReentrantCall(); +/// @notice Thrown when a call to the target contract during message relay fails. +error TargetCallFailed(); + +/// @notice Thrown when attempting to use a chain ID that is not in the dependency set. +error InvalidChainId(); + /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000023 /// @title L2ToL2CrossDomainMessenger /// @notice The L2ToL2CrossDomainMessenger is a higher level abstraction on top of the CrossL2Inbox that provides /// features necessary for secure transfers ERC20 tokens between L2 chains. Messages sent through the /// L2ToL2CrossDomainMessenger on the source chain receive both replay protection as well as domain binding. -contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, TransientReentrancyAware { +contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { /// @notice Storage slot for the sender of the current cross domain message. /// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.sender")) - 1) bytes32 internal constant CROSS_DOMAIN_MESSAGE_SENDER_SLOT = @@ -64,8 +72,8 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra uint16 public constant messageVersion = uint16(0); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.7 - string public constant version = "1.0.0-beta.7"; + /// @custom:semver 1.0.0-beta.13 + string public constant version = "1.0.0-beta.13"; /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only be present in this /// mapping if it has successfully been relayed on this chain, and can therefore not be relayed again. @@ -92,12 +100,6 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra /// @param messageHash Hash of the message that was relayed. event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash); - /// @notice Emitted whenever a message fails to be relayed on this chain. - /// @param source Chain ID of the source chain. - /// @param messageNonce Nonce associated with the messsage sent - /// @param messageHash Hash of the message that failed to be relayed. - event FailedRelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash); - /// @notice Retrieves the sender of the current cross domain message. If not entered, reverts. /// @return sender_ Address of the sender of the current cross domain message. function crossDomainMessageSender() external view onlyEntered returns (address sender_) { @@ -114,6 +116,16 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra } } + /// @notice Retrieves the context of the current cross domain message. If not entered, reverts. + /// @return sender_ Address of the sender of the current cross domain message. + /// @return source_ Chain ID of the source of the current cross domain message. + function crossDomainMessageContext() external view onlyEntered returns (address sender_, uint256 source_) { + assembly { + sender_ := tload(CROSS_DOMAIN_MESSAGE_SENDER_SLOT) + source_ := tload(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT) + } + } + /// @notice Sends a message to some target address on a destination chain. Note that if the call always reverts, /// then the message will be unrelayable and any ETH sent will be permanently locked. The same will occur /// if the target on the other chain is considered unsafe (see the _isUnsafeTarget() function). @@ -125,6 +137,7 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra if (_destination == block.chainid) revert MessageDestinationSameChain(); if (_target == Predeploys.CROSS_L2_INBOX) revert MessageTargetCrossL2Inbox(); if (_target == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert MessageTargetL2ToL2CrossDomainMessenger(); + if (!IDependencySet(Predeploys.L1_BLOCK_ATTRIBUTES).isInDependencySet(_destination)) revert InvalidChainId(); uint256 nonce = messageNonce(); emit SentMessage(_destination, _target, nonce, msg.sender, _message); @@ -146,14 +159,7 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra /// currently being replayed. /// @param _id Identifier of the SentMessage event to be relayed /// @param _sentMessage Message payload of the `SentMessage` event - function relayMessage( - ICrossL2Inbox.Identifier calldata _id, - bytes calldata _sentMessage - ) - external - payable - nonReentrant - { + function relayMessage(Identifier calldata _id, bytes calldata _sentMessage) external payable nonReentrant { // Ensure the log came from the messenger. Since the log origin is the CDM, there isn't a scenario where // this can be invoked from the CrossL2Inbox as the SentMessage log is not calldata for this function if (_id.origin != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { @@ -161,7 +167,7 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra } // Signal that this is a cross chain call that needs to have the identifier validated - CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_sentMessage)); + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_sentMessage)); // Decode the payload (uint256 destination, address target, uint256 nonce, address sender, bytes memory message) = @@ -190,13 +196,13 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra bool success = SafeCall.call(target, msg.value, message); - if (success) { - successfulMessages[messageHash] = true; - emit RelayedMessage(source, nonce, messageHash); - } else { - emit FailedRelayedMessage(source, nonce, messageHash); + if (!success) { + revert TargetCallFailed(); } + successfulMessages[messageHash] = true; + emit RelayedMessage(source, nonce, messageHash); + _storeMessageMetadata(0, address(0)); } diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 6b500e2b4dfce..53ba32dc051b0 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -1,34 +1,35 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { Predeploys } from "src/libraries/Predeploys.sol"; -import { ERC20 } from "@solady/tokens/ERC20.sol"; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +// Contracts import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; -import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol"; -/// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge. -error OnlyBridge(); +// Interfaces +import { IOptimismSuperchainERC20 } from "interfaces/L2/IOptimismSuperchainERC20.sol"; /// @custom:proxied true /// @title OptimismSuperchainERC20 /// @notice OptimismSuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the L2StandardBridge to burn -/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a SuperchainERC20 -/// token, turning it fungible and interoperable across the superchain. Likewise, it also enables the inverse -/// conversion path. +/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a +/// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it +/// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is - IOptimismSuperchainERC20Extension, - SuperchainERC20, - ISemver, - Initializable, - ERC165 -{ - /// @notice Address of the StandardBridge Predeploy. - address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; +contract OptimismSuperchainERC20 is SuperchainERC20, Initializable { + /// @notice Emitted whenever tokens are minted for an account. + /// @param to Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event Mint(address indexed to, uint256 amount); + + /// @notice Emitted whenever tokens are burned from an account. + /// @param from Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event Burn(address indexed from, uint256 amount); /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); @@ -55,15 +56,15 @@ contract OptimismSuperchainERC20 is } } - /// @notice A modifier that only allows the bridge to call - modifier onlyBridge() { - if (msg.sender != BRIDGE) revert OnlyBridge(); + /// @notice A modifier that only allows the L2StandardBridge to call + modifier onlyL2StandardBridge() { + if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) revert Unauthorized(); _; } /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.5 - string public constant version = "1.0.0-beta.5"; + /// @custom:semver 1.0.0-beta.12 + string public constant override version = "1.0.0-beta.12"; /// @notice Constructs the OptimismSuperchainERC20 contract. constructor() { @@ -94,7 +95,7 @@ contract OptimismSuperchainERC20 is /// @notice Allows the L2StandardBridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external virtual onlyBridge { + function mint(address _to, uint256 _amount) external virtual onlyL2StandardBridge { if (_to == address(0)) revert ZeroAddress(); _mint(_to, _amount); @@ -105,7 +106,7 @@ contract OptimismSuperchainERC20 is /// @notice Allows the L2StandardBridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external virtual onlyBridge { + function burn(address _from, uint256 _amount) external virtual onlyL2StandardBridge { if (_from == address(0)) revert ZeroAddress(); _burn(_from, _amount); @@ -114,7 +115,7 @@ contract OptimismSuperchainERC20 is } /// @notice Returns the address of the corresponding version of this token on the remote chain. - function remoteToken() public view override returns (address) { + function remoteToken() public view returns (address) { return _getStorage().remoteToken; } @@ -142,7 +143,11 @@ contract OptimismSuperchainERC20 is /// @param _interfaceId Interface ID to check. /// @return Whether or not the interface is supported by this contract. function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - return - _interfaceId == type(IOptimismSuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId); + return _interfaceId == type(IOptimismSuperchainERC20).interfaceId || super.supportsInterface(_interfaceId); + } + + /// @notice Sets Permit2 contract's allowance at infinity. + function _givePermit2InfiniteAllowance() internal view virtual override returns (bool) { + return true; } } diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol index d1160819a2ece..58cc15c7b9e02 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Beacon.sol @@ -1,27 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Interfaces import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; -/// @custom:proxied +/// @custom:proxied true /// @custom:predeployed 0x4200000000000000000000000000000000000027 /// @title OptimismSuperchainERC20Beacon /// @notice OptimismSuperchainERC20Beacon is the beacon proxy for the OptimismSuperchainERC20 implementation. contract OptimismSuperchainERC20Beacon is IBeacon, ISemver { - /// @notice Address of the OptimismSuperchainERC20 implementation. - address internal immutable IMPLEMENTATION; - /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.1 - string public constant version = "1.0.0-beta.1"; - - constructor(address _implementation) { - IMPLEMENTATION = _implementation; - } + /// @custom:semver 1.0.0-beta.4 + string public constant version = "1.0.0-beta.4"; /// @inheritdoc IBeacon - function implementation() external view override returns (address) { - return IMPLEMENTATION; + function implementation() external pure override returns (address) { + return Predeploys.OPTIMISM_SUPERCHAIN_ERC20; } } diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index d144f4a8b5052..8430cdcd2b049 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -1,25 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; -import { Predeploys } from "src/libraries/Predeploys.sol"; +// Contracts import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; + +// Libraries import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @custom:proxied /// @custom:predeployed 0x4200000000000000000000000000000000000026 /// @title OptimismSuperchainERC20Factory /// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies /// using CREATE3. -contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { - /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. - /// This is used to keep track of the token deployments. - mapping(address _superchainToken => address remoteToken_) public deployments; - +contract OptimismSuperchainERC20Factory is ISemver { /// @notice Emitted when an OptimismSuperchainERC20 is deployed. - /// @param superchainToken Address of the SuperchainERC20 deployment. + /// @param superchainToken Address of the OptimismSuperchainERC20 deployment. /// @param remoteToken Address of the corresponding token on the remote chain. /// @param deployer Address of the account that deployed the token. event OptimismSuperchainERC20Created( @@ -27,8 +27,12 @@ contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { ); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.3 - string public constant version = "1.0.0-beta.3"; + /// @custom:semver 1.0.0-beta.6 + string public constant version = "1.0.0-beta.6"; + + /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. + /// This is used to keep track of the token deployments. + mapping(address _localToken => address remoteToken_) public deployments; /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. /// @param _remoteToken Address of the remote token. diff --git a/packages/contracts-bedrock/src/L2/SequencerFeeVault.sol b/packages/contracts-bedrock/src/L2/SequencerFeeVault.sol index 69a78219e5bd0..e5089ff33ac87 100644 --- a/packages/contracts-bedrock/src/L2/SequencerFeeVault.sol +++ b/packages/contracts-bedrock/src/L2/SequencerFeeVault.sol @@ -1,19 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Contracts import { FeeVault } from "src/L2/FeeVault.sol"; +// Libraries import { Types } from "src/libraries/Types.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000011 /// @title SequencerFeeVault /// @notice The SequencerFeeVault is the contract that holds any fees paid to the Sequencer during /// transaction processing and block production. contract SequencerFeeVault is FeeVault, ISemver { - /// @custom:semver 1.5.0-beta.3 - string public constant version = "1.5.0-beta.3"; + /// @custom:semver 1.5.0-beta.5 + string public constant version = "1.5.0-beta.5"; /// @notice Constructs the SequencerFeeVault contract. /// @param _recipient Wallet that will receive the fees. diff --git a/packages/contracts-bedrock/src/L2/SoulGasToken.sol b/packages/contracts-bedrock/src/L2/SoulGasToken.sol new file mode 100644 index 0000000000000..417f4a64da478 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/SoulGasToken.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { Constants } from "src/libraries/Constants.sol"; + +/// @title SoulGasToken +/// @notice The SoulGasToken is a soul-bounded ERC20 contract which can be used to pay gas on L2. +/// It has 2 modes: +/// 1. when IS_BACKED_BY_NATIVE(or in other words: SoulQKC mode), the token can be minted by +/// anyone depositing native token into the contract. +/// 2. when !IS_BACKED_BY_NATIVE(or in other words: SoulETH mode), the token can only be +/// minted by whitelist minters specified by contract owner. +contract SoulGasToken is ERC20Upgradeable, OwnableUpgradeable { + /// @custom:storage-location erc7201:openzeppelin.storage.SoulGasToken + struct SoulGasTokenStorage { + // minters are whitelist EOAs, only used when !IS_BACKED_BY_NATIVE + mapping(address => bool) minters; + // burners are whitelist EOAs to burn/withdraw SoulGasToken + mapping(address => bool) burners; + // allowSgtValue are whitelist contracts to consume sgt as msg.value + // when IS_BACKED_BY_NATIVE + mapping(address => bool) allowSgtValue; + } + + /// @notice Emitted when sgt as msg.value is enabled for a contract. + /// @param from Address of the contract for which sgt as msg.value is enabled. + event AllowSgtValue(address indexed from); + /// @notice Emitted when sgt as msg.value is disabled for a contract. + /// @param from Address of the contract for which sgt as msg.value is disabled. + event DisallowSgtValue(address indexed from); + + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.SoulGasToken")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant _SOULGASTOKEN_STORAGE_LOCATION = + 0x135c38e215d95c59dcdd8fe622dccc30d04cacb8c88c332e4e7441bac172dd00; + + bool internal immutable IS_BACKED_BY_NATIVE; + + function _getSoulGasTokenStorage() private pure returns (SoulGasTokenStorage storage $) { + assembly { + $.slot := _SOULGASTOKEN_STORAGE_LOCATION + } + } + + constructor(bool _isBackedByNative) { + IS_BACKED_BY_NATIVE = _isBackedByNative; + initialize("", "", msg.sender); + } + + /// @notice Initializer. + function initialize(string memory _name, string memory _symbol, address _owner) public initializer { + __Ownable_init(); + transferOwnership(_owner); + + // initialize the inherited ERC20Upgradeable + __ERC20_init(_name, _symbol); + } + + /// @notice deposit can be called by anyone to deposit native token for SoulGasToken when + /// IS_BACKED_BY_NATIVE. + function deposit() external payable { + require(IS_BACKED_BY_NATIVE, "SGT: deposit should only be called when IS_BACKED_BY_NATIVE"); + + _mint(_msgSender(), msg.value); + } + + /// @notice batchDepositFor can be called by anyone to deposit native token for SoulGasToken in batch when + /// IS_BACKED_BY_NATIVE. + function batchDepositFor(address[] calldata _accounts, uint256[] calldata _values) external payable { + require(_accounts.length == _values.length, "SGT: invalid arguments"); + + require(IS_BACKED_BY_NATIVE, "SGT: batchDepositFor should only be called when IS_BACKED_BY_NATIVE"); + + uint256 totalValue = 0; + for (uint256 i = 0; i < _accounts.length; i++) { + _mint(_accounts[i], _values[i]); + totalValue += _values[i]; + } + require(msg.value == totalValue, "SGT: unexpected msg.value"); + } + + /// @notice batchDepositForAll is similar to batchDepositFor, but the value is the same for all accounts. + function batchDepositForAll(address[] calldata _accounts, uint256 _value) external payable { + require(IS_BACKED_BY_NATIVE, "SGT: batchDepositForAll should only be called when IS_BACKED_BY_NATIVE"); + + for (uint256 i = 0; i < _accounts.length; i++) { + _mint(_accounts[i], _value); + } + require(msg.value == _value * _accounts.length, "SGT: unexpected msg.value"); + } + + /// @notice withdrawFrom is called by the burner to burn SoulGasToken and return the native token when + /// IS_BACKED_BY_NATIVE. + function withdrawFrom(address _account, uint256 _value) external { + require(IS_BACKED_BY_NATIVE, "SGT: withdrawFrom should only be called when IS_BACKED_BY_NATIVE"); + + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + require($.burners[_msgSender()], "SGT: not the burner"); + + _burn(_account, _value); + payable(_msgSender()).transfer(_value); + } + + /// @notice batchWithdrawFrom is the batch version of withdrawFrom. + function batchWithdrawFrom(address[] calldata _accounts, uint256[] calldata _values) external { + require(_accounts.length == _values.length, "SGT: invalid arguments"); + + require(IS_BACKED_BY_NATIVE, "SGT: batchWithdrawFrom should only be called when IS_BACKED_BY_NATIVE"); + + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + require($.burners[_msgSender()], "SGT: not the burner"); + + uint256 totalValue = 0; + for (uint256 i = 0; i < _accounts.length; i++) { + _burn(_accounts[i], _values[i]); + totalValue += _values[i]; + } + + payable(_msgSender()).transfer(totalValue); + } + + /// @notice batchMint is called: + /// 1. by EOA minters to mint SoulGasToken in batch when !IS_BACKED_BY_NATIVE. + /// 2. by DEPOSITOR_ACCOUNT to refund SoulGasToken + function batchMint(address[] calldata _accounts, uint256[] calldata _values) external { + require(_accounts.length == _values.length, "SGT: invalid arguments"); + + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + require(_msgSender() == Constants.DEPOSITOR_ACCOUNT || $.minters[_msgSender()], "SGT: not a minter"); + + for (uint256 i = 0; i < _accounts.length; i++) { + _mint(_accounts[i], _values[i]); + } + } + + /// @notice addMinters is called by the owner to add minters when !IS_BACKED_BY_NATIVE. + function addMinters(address[] calldata _minters) external onlyOwner { + require(!IS_BACKED_BY_NATIVE, "SGT: addMinters should only be called when !IS_BACKED_BY_NATIVE"); + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + uint256 i; + for (i = 0; i < _minters.length; i++) { + $.minters[_minters[i]] = true; + } + } + + /// @notice delMinters is called by the owner to delete minters when !IS_BACKED_BY_NATIVE. + function delMinters(address[] calldata _minters) external onlyOwner { + require(!IS_BACKED_BY_NATIVE, "SGT: delMinters should only be called when !IS_BACKED_BY_NATIVE"); + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + uint256 i; + for (i = 0; i < _minters.length; i++) { + delete $.minters[_minters[i]]; + } + } + + /// @notice addBurners is called by the owner to add burners. + function addBurners(address[] calldata _burners) external onlyOwner { + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + uint256 i; + for (i = 0; i < _burners.length; i++) { + $.burners[_burners[i]] = true; + } + } + + /// @notice delBurners is called by the owner to delete burners. + function delBurners(address[] calldata _burners) external onlyOwner { + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + uint256 i; + for (i = 0; i < _burners.length; i++) { + delete $.burners[_burners[i]]; + } + } + + /// @notice allowSgtValue is called by the owner to enable whitelist contracts to consume sgt as msg.value + function allowSgtValue(address[] calldata _contracts) external onlyOwner { + require(IS_BACKED_BY_NATIVE, "SGT: allowSgtValue should only be called when IS_BACKED_BY_NATIVE"); + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + uint256 i; + for (i = 0; i < _contracts.length; i++) { + $.allowSgtValue[_contracts[i]] = true; + emit AllowSgtValue(_contracts[i]); + } + } + + /// @notice allowSgtValue is called by the owner to disable whitelist contracts to consume sgt as msg.value + function disallowSgtValue(address[] calldata _contracts) external onlyOwner { + require(IS_BACKED_BY_NATIVE, "SGT: disallowSgtValue should only be called when IS_BACKED_BY_NATIVE"); + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + uint256 i; + for (i = 0; i < _contracts.length; i++) { + $.allowSgtValue[_contracts[i]] = false; + emit DisallowSgtValue(_contracts[i]); + } + } + + /// @notice chargeFromOrigin is called when IS_BACKED_BY_NATIVE to charge for native balance + /// from tx.origin if caller is whitelisted. + function chargeFromOrigin(uint256 _amount) external returns (uint256 amountCharged_) { + require(IS_BACKED_BY_NATIVE, "SGT: chargeFromOrigin should only be called when IS_BACKED_BY_NATIVE"); + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + require($.allowSgtValue[_msgSender()], "SGT: caller is not whitelisted"); + uint256 balance = balanceOf(tx.origin); + if (balance == 0) { + amountCharged_ = 0; + return amountCharged_; + } + if (balance >= _amount) { + amountCharged_ = _amount; + } else { + amountCharged_ = balance; + } + _burn(tx.origin, amountCharged_); + payable(_msgSender()).transfer(amountCharged_); + } + + /// @notice burnFrom is called when !IS_BACKED_BY_NATIVE: + /// 1. by the burner to burn SoulGasToken. + /// 2. by DEPOSITOR_ACCOUNT to burn SoulGasToken. + function burnFrom(address _account, uint256 _value) external { + require(!IS_BACKED_BY_NATIVE, "SGT: burnFrom should only be called when !IS_BACKED_BY_NATIVE"); + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + require(_msgSender() == Constants.DEPOSITOR_ACCOUNT || $.burners[_msgSender()], "SGT: not the burner"); + _burn(_account, _value); + } + + /// @notice batchBurnFrom is the batch version of burnFrom. + function batchBurnFrom(address[] calldata _accounts, uint256[] calldata _values) external { + require(_accounts.length == _values.length, "SGT: invalid arguments"); + require(!IS_BACKED_BY_NATIVE, "SGT: batchBurnFrom should only be called when !IS_BACKED_BY_NATIVE"); + SoulGasTokenStorage storage $ = _getSoulGasTokenStorage(); + require(_msgSender() == Constants.DEPOSITOR_ACCOUNT || $.burners[_msgSender()], "SGT: not the burner"); + + for (uint256 i = 0; i < _accounts.length; i++) { + _burn(_accounts[i], _values[i]); + } + } + + /// @notice transferFrom is disabled for SoulGasToken. + function transfer(address, uint256) public virtual override returns (bool) { + revert("SGT: transfer is disabled for SoulGasToken"); + } + + /// @notice transferFrom is disabled for SoulGasToken. + function transferFrom(address, address, uint256) public virtual override returns (bool) { + revert("SGT: transferFrom is disabled for SoulGasToken"); + } + + /// @notice approve is disabled for SoulGasToken. + function approve(address, uint256) public virtual override returns (bool) { + revert("SGT: approve is disabled for SoulGasToken"); + } + + /// @notice Returns whether SoulGasToken is backed by native token. + function isBackedByNative() external view returns (bool) { + return IS_BACKED_BY_NATIVE; + } +} diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index e20b375ff891e..061c2d867b685 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -1,49 +1,53 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { ERC20 } from "@solady/tokens/ERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +// Contracts +import { ERC20 } from "@solady-v0.0.245/tokens/ERC20.sol"; + +// Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; +import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; + +// Interfaces +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IERC7802, IERC165 } from "interfaces/L2/IERC7802.sol"; /// @title SuperchainERC20 -/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token -/// bridging to make it fungible across the Superchain. It builds on top of the L2ToL2CrossDomainMessenger for -/// both replay protection and domain binding. -abstract contract SuperchainERC20 is ISuperchainERC20Extensions, ISuperchainERC20Errors, ERC20 { - /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. - address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - - /// @notice Sends tokens to some target address on another chain. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. - function sendERC20(address _to, uint256 _amount, uint256 _chainId) external virtual { - if (_to == address(0)) revert ZeroAddress(); - - _burn(msg.sender, _amount); - - bytes memory _message = abi.encodeCall(this.relayERC20, (msg.sender, _to, _amount)); - IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), _message); - - emit SendERC20(msg.sender, _to, _amount, _chainId); +/// @notice A standard ERC20 extension implementing IERC7802 for unified cross-chain fungibility across +/// the Superchain. Allows the SuperchainTokenBridge to mint and burn tokens as needed. +abstract contract SuperchainERC20 is ERC20, IERC7802, ISemver { + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.8 + function version() external view virtual returns (string memory) { + return "1.0.0-beta.8"; } - /// @notice Relays tokens received from another chain. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. - function relayERC20(address _from, address _to, uint256 _amount) external virtual { - if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); + /// @notice Allows the SuperchainTokenBridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function crosschainMint(address _to, uint256 _amount) external { + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); + + _mint(_to, _amount); + + emit CrosschainMint(_to, _amount, msg.sender); + } - if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { - revert InvalidCrossDomainSender(); - } + /// @notice Allows the SuperchainTokenBridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function crosschainBurn(address _from, uint256 _amount) external { + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); - uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); + _burn(_from, _amount); - _mint(_to, _amount); + emit CrosschainBurn(_from, _amount, msg.sender); + } - emit RelayERC20(_from, _to, _amount, source); + /// @inheritdoc IERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { + return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC20).interfaceId + || _interfaceId == type(IERC165).interfaceId; } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol new file mode 100644 index 0000000000000..fc8c3d9610886 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol"; + +// Interfaces +import { ISuperchainERC20 } from "interfaces/L2/ISuperchainERC20.sol"; +import { IERC7802, IERC165 } from "interfaces/L2/IERC7802.sol"; +import { IL2ToL2CrossDomainMessenger } from "interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; + +/// @custom:proxied true +/// @custom:predeploy 0x4200000000000000000000000000000000000028 +/// @title SuperchainTokenBridge +/// @notice The SuperchainTokenBridge allows for the bridging of ERC20 tokens to make them fungible across the +/// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain +/// binding. +contract SuperchainTokenBridge { + /// @notice Thrown when attempting to relay a message and the cross domain message sender is not the + /// SuperchainTokenBridge. + error InvalidCrossDomainSender(); + + /// @notice Thrown when attempting to use a token that does not implement the ERC7802 interface. + error InvalidERC7802(); + + /// @notice Emitted when tokens are sent from one chain to another. + /// @param token Address of the token sent. + /// @param from Address of the sender. + /// @param to Address of the recipient. + /// @param amount Number of tokens sent. + /// @param destination Chain ID of the destination chain. + event SendERC20( + address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination + ); + + /// @notice Emitted whenever tokens are successfully relayed on this chain. + /// @param token Address of the token relayed. + /// @param from Address of the msg.sender of sendERC20 on the source chain. + /// @param to Address of the recipient. + /// @param amount Amount of tokens relayed. + /// @param source Chain ID of the source chain. + event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); + + /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. + address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.4 + string public constant version = "1.0.0-beta.4"; + + /// @notice Sends tokens to a target address on another chain. + /// @dev Tokens are burned on the source chain. + /// @param _token Token to send. + /// @param _to Address to send tokens to. + /// @param _amount Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + /// @return msgHash_ Hash of the message sent. + function sendERC20( + address _token, + address _to, + uint256 _amount, + uint256 _chainId + ) + external + returns (bytes32 msgHash_) + { + if (_to == address(0)) revert ZeroAddress(); + + if (!IERC165(_token).supportsInterface(type(IERC7802).interfaceId)) revert InvalidERC7802(); + + ISuperchainERC20(_token).crosschainBurn(msg.sender, _amount); + + bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); + msgHash_ = IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); + + emit SendERC20(_token, msg.sender, _to, _amount, _chainId); + } + + /// @notice Relays tokens received from another chain. + /// @dev Tokens are minted on the destination chain. + /// @param _token Token to relay. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _to Address to relay tokens to. + /// @param _amount Amount of tokens to relay. + function relayERC20(address _token, address _from, address _to, uint256 _amount) external { + if (msg.sender != MESSENGER) revert Unauthorized(); + + (address crossDomainMessageSender, uint256 source) = + IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageContext(); + + if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender(); + + ISuperchainERC20(_token).crosschainMint(_to, _amount); + + emit RelayERC20(_token, _from, _to, _amount, source); + } +} diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index 4788b70b1bc9e..ab6ff44a33aba 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -5,24 +5,46 @@ pragma solidity 0.8.15; import { WETH98 } from "src/universal/WETH98.sol"; // Libraries -import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; +import { NotCustomGasToken, Unauthorized, ZeroAddress } from "src/libraries/errors/CommonErrors.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; +import { Preinstalls } from "src/libraries/Preinstalls.sol"; +import { SafeSend } from "src/universal/SafeSend.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; -import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; - +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IL2ToL2CrossDomainMessenger } from "interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; +import { IETHLiquidity } from "interfaces/L2/IETHLiquidity.sol"; +import { IERC7802, IERC165 } from "interfaces/L2/IERC7802.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @custom:proxied true +/// @custom:predeploy 0x4200000000000000000000000000000000000024 /// @title SuperchainWETH /// @notice SuperchainWETH is a version of WETH that can be freely transfrered between chains /// within the superchain. SuperchainWETH can be converted into native ETH on chains that /// do not use a custom gas token. -contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { +contract SuperchainWETH is WETH98, IERC7802, ISemver { + /// @notice Thrown when attempting to relay a message and the cross domain message sender is not SuperchainWETH. + error InvalidCrossDomainSender(); + + /// @notice Emitted when ETH is sent from one chain to another. + /// @param from Address of the sender. + /// @param to Address of the recipient. + /// @param amount Amount of ETH sent. + /// @param destination Chain ID of the destination chain. + event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination); + + /// @notice Emitted whenever ETH is successfully relayed on this chain. + /// @param from Address of the msg.sender of sendETH on the source chain. + /// @param to Address of the recipient. + /// @param amount Amount of ETH relayed. + /// @param source Chain ID of the source chain. + event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source); + /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.5 - string public constant version = "1.0.0-beta.5"; + /// @custom:semver 1.0.0-beta.13 + string public constant version = "1.0.0-beta.13"; /// @inheritdoc WETH98 function deposit() public payable override { @@ -31,68 +53,119 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { } /// @inheritdoc WETH98 - function withdraw(uint256 wad) public override { + function withdraw(uint256 _amount) public override { if (IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) revert NotCustomGasToken(); - super.withdraw(wad); + super.withdraw(_amount); } - /// @inheritdoc ISuperchainERC20Extensions - function sendERC20(address dst, uint256 wad, uint256 chainId) public { - // Burn from user's balance. - _burn(msg.sender, wad); + /// @inheritdoc WETH98 + function allowance(address owner, address spender) public view override returns (uint256) { + if (spender == Preinstalls.Permit2) return type(uint256).max; + return super.allowance(owner, spender); + } - // Burn to ETHLiquidity contract. + /// @notice Mints WETH to an address. + /// @param _to The address to mint WETH to. + /// @param _amount The amount of WETH to mint. + function _mint(address _to, uint256 _amount) internal { + _balanceOf[_to] += _amount; + emit Transfer(address(0), _to, _amount); + } + + /// @notice Burns WETH from an address. + /// @param _from The address to burn WETH from. + /// @param _amount The amount of WETH to burn. + function _burn(address _from, uint256 _amount) internal { + _balanceOf[_from] -= _amount; + emit Transfer(_from, address(0), _amount); + } + + /// @notice Allows the SuperchainTokenBridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function crosschainMint(address _to, uint256 _amount) external { + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); + + _mint(_to, _amount); + + // Withdraw from ETHLiquidity contract. if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { - IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: wad }(); + // NOTE: 'mint' will soon change to 'withdraw'. + IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(_amount); } - // Send message to other chain. - IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).sendMessage({ - _destination: chainId, - _target: address(this), - _message: abi.encodeCall(this.relayERC20, (msg.sender, dst, wad)) - }); - - // Emit event. - emit SendERC20(msg.sender, dst, wad, chainId); + emit CrosschainMint(_to, _amount, msg.sender); } - /// @inheritdoc ISuperchainERC20Extensions - function relayERC20(address from, address dst, uint256 wad) external { - // Receive message from other chain. - IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - if (msg.sender != address(messenger)) revert Unauthorized(); - if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized(); + /// @notice Allows the SuperchainTokenBridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function crosschainBurn(address _from, uint256 _amount) external { + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); - // Mint from ETHLiquidity contract. + _burn(_from, _amount); + + // Deposit to ETHLiquidity contract. if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { - IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(wad); + // NOTE: 'burn' will soon change to 'deposit'. + IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: _amount }(); } - // Get source chain ID. - uint256 source = messenger.crossDomainMessageSource(); - - // Mint to user's balance. - _mint(dst, wad); + emit CrosschainBurn(_from, _amount, msg.sender); + } - // Emit event. - emit RelayERC20(from, dst, wad, source); + /// @inheritdoc IERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { + return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC20).interfaceId + || _interfaceId == type(IERC165).interfaceId; } - /// @notice Mints WETH to an address. - /// @param guy The address to mint WETH to. - /// @param wad The amount of WETH to mint. - function _mint(address guy, uint256 wad) internal { - balanceOf[guy] += wad; - emit Transfer(address(0), guy, wad); + /// @notice Sends ETH to some target address on another chain. + /// @param _to Address to send ETH to. + /// @param _chainId Chain ID of the destination chain. + /// @return msgHash_ Hash of the message sent. + function sendETH(address _to, uint256 _chainId) external payable returns (bytes32 msgHash_) { + if (_to == address(0)) revert ZeroAddress(); + + if (IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { + revert NotCustomGasToken(); + } + + // NOTE: 'burn' will soon change to 'deposit'. + IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: msg.value }(); + + msgHash_ = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).sendMessage({ + _destination: _chainId, + _target: address(this), + _message: abi.encodeCall(this.relayETH, (msg.sender, _to, msg.value)) + }); + + emit SendETH(msg.sender, _to, msg.value, _chainId); } - /// @notice Burns WETH from an address. - /// @param guy The address to burn WETH from. - /// @param wad The amount of WETH to burn. - function _burn(address guy, uint256 wad) internal { - require(balanceOf[guy] >= wad); - balanceOf[guy] -= wad; - emit Transfer(guy, address(0), wad); + /// @notice Relays ETH received from another chain. + /// @param _from Address of the msg.sender of sendETH on the source chain. + /// @param _to Address to relay ETH to. + /// @param _amount Amount of ETH to relay. + function relayETH(address _from, address _to, uint256 _amount) external { + if (msg.sender != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert Unauthorized(); + + (address crossDomainMessageSender, uint256 source) = + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageContext(); + + if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender(); + + if (IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { + // Since ETH is not the native asset on custom gas token chains, send SuperchainWETH to the recipient. + _mint(_to, _amount); + } else { + // NOTE: 'mint' will soon change to 'withdraw'. + IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(_amount); + + // This is a forced ETH send to the recipient, the recipient should NOT expect to be called. + new SafeSend{ value: _amount }(payable(_to)); + } + + emit RelayETH(_from, _to, _amount, source); } } diff --git a/packages/contracts-bedrock/src/L2/WETH.sol b/packages/contracts-bedrock/src/L2/WETH.sol index fb24f07473385..5dc716fca569b 100644 --- a/packages/contracts-bedrock/src/L2/WETH.sol +++ b/packages/contracts-bedrock/src/L2/WETH.sol @@ -8,14 +8,14 @@ import { WETH98 } from "src/universal/WETH98.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @title WETH contract that reads the name and symbol from the L1Block contract. /// Allows for nice rendering of token names for chains using custom gas token. contract WETH is WETH98, ISemver { - /// @custom:semver 1.1.0-beta.2 - string public constant version = "1.1.0-beta.2"; + /// @custom:semver 1.1.0-beta.4 + string public constant version = "1.1.0-beta.4"; /// @notice Returns the name of the wrapped native asset. Will be "Wrapped Ether" /// if the native asset is Ether. diff --git a/packages/contracts-bedrock/src/L2/interfaces/ICrossL2Inbox.sol b/packages/contracts-bedrock/src/L2/interfaces/ICrossL2Inbox.sol deleted file mode 100644 index 3267122fc0b10..0000000000000 --- a/packages/contracts-bedrock/src/L2/interfaces/ICrossL2Inbox.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title ICrossL2Inbox -/// @notice Interface for the CrossL2Inbox contract. -interface ICrossL2Inbox { - /// @notice The struct for a pointer to a message payload in a remote (or local) chain. - struct Identifier { - address origin; - uint256 blockNumber; - uint256 logIndex; - uint256 timestamp; - uint256 chainId; - } - - /// @notice Returns the interop start timestamp. - /// @return interopStart_ interop start timestamp. - function interopStart() external view returns (uint256 interopStart_); - - /// @notice Returns the origin address of the Identifier. - /// @return origin_ The origin address of the Identifier. - function origin() external view returns (address origin_); - - /// @notice Returns the block number of the Identifier. - /// @return blockNumber_ The block number of the Identifier. - function blockNumber() external view returns (uint256 blockNumber_); - - /// @notice Returns the log index of the Identifier. - /// @return logIndex_ The log index of the Identifier. - function logIndex() external view returns (uint256 logIndex_); - - /// @notice Returns the timestamp of the Identifier. - /// @return timestamp_ The timestamp of the Identifier. - function timestamp() external view returns (uint256 timestamp_); - - /// @notice Returns the chain ID of the Identifier. - /// @return chainId_ The chain ID of the Identifier. - function chainId() external view returns (uint256 chainId_); - - /// @notice Executes a cross chain message on the destination chain. - /// @param _id An Identifier pointing to the initiating message. - /// @param _target Account that is called with _msg. - /// @param _msg The message payload, matching the initiating message. - function executeMessage( - ICrossL2Inbox.Identifier calldata _id, - address _target, - bytes calldata _msg - ) - external - payable; - - /// @notice Validates a cross chain message on the destination chain - /// and emits an ExecutingMessage event. This function is useful - /// for applications that understand the schema of the _message payload and want to - /// process it in a custom way. - /// @param _id Identifier of the message. - /// @param _msgHash Hash of the message payload to call target with. - function validateMessage(Identifier calldata _id, bytes32 _msgHash) external; -} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol deleted file mode 100644 index 2b5b945dec73e..0000000000000 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ICrossL2Inbox } from "src/L2/interfaces/ICrossL2Inbox.sol"; - -/// @title IL2ToL2CrossDomainMessenger -/// @notice Interface for the L2ToL2CrossDomainMessenger contract. -interface IL2ToL2CrossDomainMessenger { - /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only - /// be present in this mapping if it has successfully been relayed on this chain, and - /// can therefore not be relayed again. - /// @param _msgHash message hash to check. - /// @return Returns true if the message corresponding to the `_msgHash` was successfully relayed. - function successfulMessages(bytes32 _msgHash) external view returns (bool); - - /// @notice Retrieves the next message nonce. Message version will be added to the upper two - /// bytes of the message nonce. Message version allows us to treat messages as having - /// different structures. - /// @return Nonce of the next message to be sent, with added message version. - function messageNonce() external view returns (uint256); - - /// @notice Retrieves the sender of the current cross domain message. - /// @return sender_ Address of the sender of the current cross domain message. - function crossDomainMessageSender() external view returns (address sender_); - - /// @notice Retrieves the source of the current cross domain message. - /// @return source_ Chain ID of the source of the current cross domain message. - function crossDomainMessageSource() external view returns (uint256 source_); - - /// @notice Sends a message to some target address on a destination chain. Note that if the call - /// always reverts, then the message will be unrelayable, and any ETH sent will be - /// permanently locked. The same will occur if the target on the other chain is - /// considered unsafe (see the _isUnsafeTarget() function). - /// @param _destination Chain ID of the destination chain. - /// @param _target Target contract or wallet address. - /// @param _message Message to trigger the target address with. - /// @return msgHash_ The hash of the message being sent, which can be used for tracking whether - /// the message has successfully been relayed. - function sendMessage( - uint256 _destination, - address _target, - bytes calldata _message - ) - external - returns (bytes32 msgHash_); - - /// @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only - /// be executed via cross-chain call from the other messenger OR if the message was - /// already received once and is currently being replayed. - /// @param _id Identifier of the SentMessage event to be relayed - /// @param _sentMessage Message payload of the `SentMessage` event - function relayMessage(ICrossL2Inbox.Identifier calldata _id, bytes calldata _sentMessage) external payable; -} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol deleted file mode 100644 index 5f537c1f51ec1..0000000000000 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Interfaces -import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; - -/// @title IOptimismSuperchainERC20Extension -/// @notice This interface is available on the OptimismSuperchainERC20 contract. -/// We declare it as a separate interface so that it can be used in -/// custom implementations of SuperchainERC20. -interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISuperchainERC20Errors { - /// @notice Emitted whenever tokens are minted for an account. - /// @param account Address of the account tokens are being minted for. - /// @param amount Amount of tokens minted. - event Mint(address indexed account, uint256 amount); - - /// @notice Emitted whenever tokens are burned from an account. - /// @param account Address of the account tokens are being burned from. - /// @param amount Amount of tokens burned. - event Burn(address indexed account, uint256 amount); - - /// @notice Allows the L2StandardBridge to mint tokens. - /// @param _to Address to mint tokens to. - /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external; - - /// @notice Allows the L2StandardBridge to burn tokens. - /// @param _from Address to burn tokens from. - /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external; - - /// @notice Returns the address of the corresponding version of this token on the remote chain. - function remoteToken() external view returns (address); -} - -/// @title IOptimismSuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the OptimismSuperchainERC20Extension interface. -interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Beacon.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Beacon.sol deleted file mode 100644 index a0da4e5d230a9..0000000000000 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Beacon.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; - -/// @title IOptimismSuperchainERC20Beacon -/// @notice Interface for the OptimismSuperchainERC20Beacon contract -interface IOptimismSuperchainERC20Beacon is IBeacon, ISemver { - function version() external view returns (string memory); - function implementation() external view override returns (address); - - function __constructor__(address _implementation) external; -} diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol deleted file mode 100644 index 6ed17a9f46ec5..0000000000000 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Interfaces -import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; - -/// @title ISuperchainERC20Extensions -/// @notice Interface for the extensions to the ERC20 standard that are used by SuperchainERC20. -/// Exists in case developers are already importing the ERC20 interface separately and -/// importing the full SuperchainERC20 interface would cause conflicting imports. -interface ISuperchainERC20Extensions { - /// @notice Emitted when tokens are sent from one chain to another. - /// @param from Address of the sender. - /// @param to Address of the recipient. - /// @param amount Number of tokens sent. - /// @param destination Chain ID of the destination chain. - event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination); - - /// @notice Emitted whenever tokens are successfully relayed on this chain. - /// @param from Address of the msg.sender of sendERC20 on the source chain. - /// @param to Address of the recipient. - /// @param amount Amount of tokens relayed. - /// @param source Chain ID of the source chain. - event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source); - - /// @notice Sends tokens to some target address on another chain. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. - function sendERC20(address _to, uint256 _amount, uint256 _chainId) external; - - /// @notice Relays tokens received from another chain. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. - function relayERC20(address _from, address _to, uint256 _amount) external; -} - -/// @title ISuperchainERC20Errors -/// @notice Interface containing the errors added in the SuperchainERC20 implementation. -interface ISuperchainERC20Errors { - /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not - /// L2ToL2CrossDomainMessenger. - error CallerNotL2ToL2CrossDomainMessenger(); - - /// @notice Thrown when attempting to relay a message and the cross domain message sender is not this - /// SuperchainERC20. - error InvalidCrossDomainSender(); - - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); -} - -/// @title ISuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extensions interface. -interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions, ISuperchainERC20Errors { - function __constructor__() external; -} diff --git a/packages/contracts-bedrock/src/cannon/MIPS.sol b/packages/contracts-bedrock/src/cannon/MIPS.sol index 7b544b25a3b80..b1fad0b788c81 100644 --- a/packages/contracts-bedrock/src/cannon/MIPS.sol +++ b/packages/contracts-bedrock/src/cannon/MIPS.sol @@ -1,14 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol"; +// Libraries import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol"; import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; import { InvalidRMWInstruction } from "src/cannon/libraries/CannonErrors.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; + /// @title MIPS /// @notice The MIPS contract emulates a single MIPS instruction. /// Note that delay slots are isolated instructions: @@ -44,8 +47,8 @@ contract MIPS is ISemver { } /// @notice The semantic version of the MIPS contract. - /// @custom:semver 1.2.1-beta.3 - string public constant version = "1.2.1-beta.3"; + /// @custom:semver 1.2.1-beta.10 + string public constant version = "1.2.1-beta.10"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; @@ -180,7 +183,7 @@ contract MIPS is ISemver { }); (v0, v1, state.preimageOffset, state.memRoot,,) = sys.handleSysRead(args); } else if (syscall_no == sys.SYS_WRITE) { - (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ + sys.SysWriteParams memory args = sys.SysWriteParams({ _a0: a0, _a1: a1, _a2: a2, @@ -189,6 +192,7 @@ contract MIPS is ISemver { _proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), _memRoot: state.memRoot }); + (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite(args); } else if (syscall_no == sys.SYS_FCNTL) { (v0, v1) = sys.handleSysFcntl(a0, a1); } diff --git a/packages/contracts-bedrock/src/cannon/MIPS2.sol b/packages/contracts-bedrock/src/cannon/MIPS2.sol index 77d3530e00015..e2af829be6e94 100644 --- a/packages/contracts-bedrock/src/cannon/MIPS2.sol +++ b/packages/contracts-bedrock/src/cannon/MIPS2.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol"; +// Libraries import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; @@ -12,6 +11,10 @@ import { InvalidMemoryProof, InvalidRMWInstruction, InvalidSecondMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; + /// @title MIPS2 /// @notice The MIPS2 contract emulates a single MIPS instruction. /// It differs from MIPS.sol in that it supports multi-threading. @@ -34,6 +37,9 @@ contract MIPS2 is ISemver { uint32[32] registers; } + uint8 internal constant LL_STATUS_NONE = 0; + uint8 internal constant LL_STATUS_ACTIVE = 1; + /// @notice Stores the VM state. /// Total state size: 32 + 32 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 172 bytes /// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot. @@ -42,7 +48,7 @@ contract MIPS2 is ISemver { bytes32 preimageKey; uint32 preimageOffset; uint32 heap; - bool llReservationActive; + uint8 llReservationStatus; uint32 llAddress; uint32 llOwnerThread; uint8 exitCode; @@ -57,8 +63,8 @@ contract MIPS2 is ISemver { } /// @notice The semantic version of the MIPS2 contract. - /// @custom:semver 1.0.0-beta.14 - string public constant version = "1.0.0-beta.14"; + /// @custom:semver 1.0.0-beta.25 + string public constant version = "1.0.0-beta.25"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; @@ -99,7 +105,39 @@ contract MIPS2 is ISemver { /// the current thread stack. /// @param _localContext The local key context for the preimage oracle. Optional, can be set as a constant /// if the caller only requires one set of local keys. - function step(bytes calldata _stateData, bytes calldata _proof, bytes32 _localContext) public returns (bytes32) { + /// @return postState_ The hash of the post state witness after the state transition. + function step( + bytes calldata _stateData, + bytes calldata _proof, + bytes32 _localContext + ) + public + returns (bytes32 postState_) + { + postState_ = doStep(_stateData, _proof, _localContext); + assertPostStateChecks(); + } + + function assertPostStateChecks() internal pure { + State memory state; + assembly { + state := STATE_MEM_OFFSET + } + + bytes32 activeStack = state.traverseRight ? state.rightThreadStack : state.leftThreadStack; + if (activeStack == EMPTY_THREAD_ROOT) { + revert("MIPS2: post-state active thread stack is empty"); + } + } + + function doStep( + bytes calldata _stateData, + bytes calldata _proof, + bytes32 _localContext + ) + internal + returns (bytes32) + { unchecked { State memory state; ThreadState memory thread; @@ -141,7 +179,7 @@ contract MIPS2 is ISemver { c, m := putField(c, m, 32) // preimageKey c, m := putField(c, m, 4) // preimageOffset c, m := putField(c, m, 4) // heap - c, m := putField(c, m, 1) // llReservationActive + c, m := putField(c, m, 1) // llReservationStatus c, m := putField(c, m, 4) // llAddress c, m := putField(c, m, 4) // llOwnerThread c, m := putField(c, m, 1) // exitCode @@ -162,8 +200,11 @@ contract MIPS2 is ISemver { return outputState(); } - if (state.leftThreadStack == EMPTY_THREAD_ROOT && state.rightThreadStack == EMPTY_THREAD_ROOT) { - revert("MIPS2: illegal vm state"); + if ( + (state.leftThreadStack == EMPTY_THREAD_ROOT && !state.traverseRight) + || (state.rightThreadStack == EMPTY_THREAD_ROOT && state.traverseRight) + ) { + revert("MIPS2: active thread stack is empty"); } state.step += 1; @@ -266,14 +307,14 @@ contract MIPS2 is ISemver { } function handleMemoryUpdate(State memory _state, uint32 _memAddr) internal pure { - if (_memAddr == _state.llAddress) { + if (_memAddr == (0xFFFFFFFC & _state.llAddress)) { // Reserved address was modified, clear the reservation clearLLMemoryReservation(_state); } } function clearLLMemoryReservation(State memory _state) internal pure { - _state.llReservationActive = false; + _state.llReservationStatus = LL_STATUS_NONE; _state.llAddress = 0; _state.llOwnerThread = 0; } @@ -292,25 +333,28 @@ contract MIPS2 is ISemver { uint32 base = _thread.registers[baseReg]; uint32 rtReg = (_insn >> 16) & 0x1F; uint32 offset = ins.signExtendImmediate(_insn); - - uint32 effAddr = (base + offset) & 0xFFFFFFFC; - uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1); - uint32 mem = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset); + uint32 addr = base + offset; uint32 retVal = 0; uint32 threadId = _thread.threadID; if (_opcode == ins.OP_LOAD_LINKED) { - retVal = mem; - _state.llReservationActive = true; - _state.llAddress = effAddr; + retVal = loadWord(_state, addr); + + _state.llReservationStatus = LL_STATUS_ACTIVE; + _state.llAddress = addr; _state.llOwnerThread = threadId; } else if (_opcode == ins.OP_STORE_CONDITIONAL) { // Check if our memory reservation is still intact - if (_state.llReservationActive && _state.llOwnerThread == threadId && _state.llAddress == effAddr) { + if ( + _state.llReservationStatus == LL_STATUS_ACTIVE && _state.llOwnerThread == threadId + && _state.llAddress == addr + ) { // Complete atomic update: set memory and return 1 for success clearLLMemoryReservation(_state); + uint32 val = _thread.registers[rtReg]; - _state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val); + storeWord(_state, addr, val); + retVal = 1; } else { // Atomic update failed, return 0 for failure @@ -329,6 +373,18 @@ contract MIPS2 is ISemver { } } + function loadWord(State memory _state, uint32 _addr) internal pure returns (uint32 val_) { + uint32 effAddr = _addr & 0xFFFFFFFC; + uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1); + val_ = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset); + } + + function storeWord(State memory _state, uint32 _addr, uint32 _val) internal pure { + uint32 effAddr = _addr & 0xFFFFFFFC; + uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1); + _state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, _val); + } + function handleSyscall(bytes32 _localContext) internal returns (bytes32 out_) { unchecked { // Load state from memory offsets to reduce stack pressure @@ -406,7 +462,7 @@ contract MIPS2 is ISemver { // Encapsulate execution to avoid stack-too-deep error (v0, v1) = execSysRead(state, args); } else if (syscall_no == sys.SYS_WRITE) { - (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ + sys.SysWriteParams memory args = sys.SysWriteParams({ _a0: a0, _a1: a1, _a2: a2, @@ -415,6 +471,7 @@ contract MIPS2 is ISemver { _proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), _memRoot: state.memRoot }); + (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite(args); } else if (syscall_no == sys.SYS_FCNTL) { (v0, v1) = sys.handleSysFcntl(a0, a1); } else if (syscall_no == sys.SYS_GETTID) { @@ -534,6 +591,8 @@ contract MIPS2 is ISemver { // ignored } else if (syscall_no == sys.SYS_PREAD64) { // ignored + } else if (syscall_no == sys.SYS_STAT) { + // ignored } else if (syscall_no == sys.SYS_FSTAT) { // ignored } else if (syscall_no == sys.SYS_OPENAT) { @@ -572,6 +631,10 @@ contract MIPS2 is ISemver { // ignored } else if (syscall_no == sys.SYS_TIMERDELETE) { // ignored + } else if (syscall_no == sys.SYS_GETRLIMIT) { + // ignored + } else if (syscall_no == sys.SYS_LSEEK) { + // ignored } else { if (syscall_no == sys.SYS_FSTAT64 || syscall_no == sys.SYS_STAT64 || syscall_no == sys.SYS_LLSEEK) { // noop @@ -629,7 +692,7 @@ contract MIPS2 is ISemver { from, to := copyMem(from, to, 32) // preimageKey from, to := copyMem(from, to, 4) // preimageOffset from, to := copyMem(from, to, 4) // heap - from, to := copyMem(from, to, 1) // llReservationActive + from, to := copyMem(from, to, 1) // llReservationStatus from, to := copyMem(from, to, 4) // llAddress from, to := copyMem(from, to, 4) // llOwnerThread let exitCode := mload(from) diff --git a/packages/contracts-bedrock/src/cannon/MIPS64.sol b/packages/contracts-bedrock/src/cannon/MIPS64.sol new file mode 100644 index 0000000000000..51e60ba37cea5 --- /dev/null +++ b/packages/contracts-bedrock/src/cannon/MIPS64.sol @@ -0,0 +1,989 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Libraries +import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; +import { MIPS64Syscalls as sys } from "src/cannon/libraries/MIPS64Syscalls.sol"; +import { MIPS64State as st } from "src/cannon/libraries/MIPS64State.sol"; +import { MIPS64Instructions as ins } from "src/cannon/libraries/MIPS64Instructions.sol"; +import { MIPS64Arch as arch } from "src/cannon/libraries/MIPS64Arch.sol"; +import { VMStatuses } from "src/dispute/lib/Types.sol"; +import { + InvalidMemoryProof, InvalidRMWInstruction, InvalidSecondMemoryProof +} from "src/cannon/libraries/CannonErrors.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; + +/// @title MIPS64 +/// @notice The MIPS64 contract emulates a single MIPS instruction. +/// It differs from MIPS.sol in that it supports MIPS64 instructions and multi-tasking. +contract MIPS64 is ISemver { + /// @notice The thread context. + /// Total state size: 8 + 1 + 1 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 32 * 8 = 322 bytes + struct ThreadState { + // metadata + uint64 threadID; + uint8 exitCode; + bool exited; + // state + uint64 futexAddr; + uint64 futexVal; + uint64 futexTimeoutStep; + uint64 pc; + uint64 nextPC; + uint64 lo; + uint64 hi; + uint64[32] registers; + } + + uint32 internal constant PACKED_THREAD_STATE_SIZE = 322; + + uint8 internal constant LL_STATUS_NONE = 0; + uint8 internal constant LL_STATUS_ACTIVE_32_BIT = 0x1; + uint8 internal constant LL_STATUS_ACTIVE_64_BIT = 0x2; + + /// @notice Stores the VM state. + /// Total state size: 32 + 32 + 8 + 8 + 1 + 8 + 8 + 1 + 1 + 8 + 8 + 8 + 1 + 32 + 32 + 8 = 196 bytes + /// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot. + struct State { + bytes32 memRoot; + bytes32 preimageKey; + uint64 preimageOffset; + uint64 heap; + uint8 llReservationStatus; + uint64 llAddress; + uint64 llOwnerThread; + uint8 exitCode; + bool exited; + uint64 step; + uint64 stepsSinceLastContextSwitch; + uint64 wakeup; + bool traverseRight; + bytes32 leftThreadStack; + bytes32 rightThreadStack; + uint64 nextThreadID; + } + + /// @notice The semantic version of the MIPS64 contract. + /// @custom:semver 1.0.0-beta.7 + string public constant version = "1.0.0-beta.7"; + + /// @notice The preimage oracle contract. + IPreimageOracle internal immutable ORACLE; + + // The offset of the start of proof calldata (_threadWitness.offset) in the step() function + uint256 internal constant THREAD_PROOF_OFFSET = 388; + + // The offset of the start of proof calldata (_memProof.offset) in the step() function + uint256 internal constant MEM_PROOF_OFFSET = THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE + 32; + + // The empty thread root - keccak256(bytes32(0) ++ bytes32(0)) + bytes32 internal constant EMPTY_THREAD_ROOT = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; + + // State memory offset allocated during step + uint256 internal constant STATE_MEM_OFFSET = 0x80; + + // ThreadState memory offset allocated during step + uint256 internal constant TC_MEM_OFFSET = 0x280; + + /// @param _oracle The address of the preimage oracle contract. + constructor(IPreimageOracle _oracle) { + ORACLE = _oracle; + } + + /// @notice Getter for the pre-image oracle contract. + /// @return oracle_ The IPreimageOracle contract. + function oracle() external view returns (IPreimageOracle oracle_) { + oracle_ = ORACLE; + } + + /// @notice Executes a single step of the multi-threaded vm. + /// Will revert if any required input state is missing. + /// @param _stateData The encoded state witness data. + /// @param _proof The encoded proof data: <, . + /// Contains the thread context witness and the memory proof data for leaves within the MIPS VM's + /// memory. + /// The thread context witness is a packed tuple of the thread context and the immediate inner root of + /// the current thread stack. + /// @param _localContext The local key context for the preimage oracle. Optional, can be set as a constant + /// if the caller only requires one set of local keys. + /// @return postState_ The hash of the post state witness after the state transition. + function step( + bytes calldata _stateData, + bytes calldata _proof, + bytes32 _localContext + ) + public + returns (bytes32 postState_) + { + postState_ = doStep(_stateData, _proof, _localContext); + assertPostStateChecks(); + } + + function assertPostStateChecks() internal pure { + State memory state; + assembly { + state := STATE_MEM_OFFSET + } + + bytes32 activeStack = state.traverseRight ? state.rightThreadStack : state.leftThreadStack; + if (activeStack == EMPTY_THREAD_ROOT) { + revert("MIPS64: post-state active thread stack is empty"); + } + } + + function doStep( + bytes calldata _stateData, + bytes calldata _proof, + bytes32 _localContext + ) + internal + returns (bytes32) + { + unchecked { + State memory state; + ThreadState memory thread; + uint32 exited; + assembly { + if iszero(eq(state, STATE_MEM_OFFSET)) { + // expected state mem offset check + revert(0, 0) + } + if iszero(eq(thread, TC_MEM_OFFSET)) { + // expected thread mem offset check + revert(0, 0) + } + if iszero(eq(mload(0x40), shl(5, 63))) { + // 4 + 16 state slots + 43 thread slots = 63 expected memory check + revert(0, 0) + } + if iszero(eq(_stateData.offset, 132)) { + // 32*4+4=132 expected state data offset + revert(0, 0) + } + if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) { + // _stateData.offset = 132 + // stateData.length = 196 + // 32-byte align padding = 28 + // _proof size prefix = 32 + // expected thread proof offset equals the sum of the above is 388 + revert(0, 0) + } + + function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut { + // calldata is packed, thus starting left-aligned, shift-right to pad and right-align + let w := shr(shl(3, sub(32, size)), calldataload(callOffset)) + mstore(memOffset, w) + callOffsetOut := add(callOffset, size) + memOffsetOut := add(memOffset, 32) + } + + // Unpack state from calldata into memory + let c := _stateData.offset // calldata offset + let m := STATE_MEM_OFFSET // mem offset + c, m := putField(c, m, 32) // memRoot + c, m := putField(c, m, 32) // preimageKey + c, m := putField(c, m, 8) // preimageOffset + c, m := putField(c, m, 8) // heap + c, m := putField(c, m, 1) // llReservationStatus + c, m := putField(c, m, 8) // llAddress + c, m := putField(c, m, 8) // llOwnerThread + c, m := putField(c, m, 1) // exitCode + c, m := putField(c, m, 1) // exited + exited := mload(sub(m, 32)) + c, m := putField(c, m, 8) // step + c, m := putField(c, m, 8) // stepsSinceLastContextSwitch + c, m := putField(c, m, 8) // wakeup + c, m := putField(c, m, 1) // traverseRight + c, m := putField(c, m, 32) // leftThreadStack + c, m := putField(c, m, 32) // rightThreadStack + c, m := putField(c, m, 8) // nextThreadID + } + st.assertExitedIsValid(exited); + + if (state.exited) { + // thread state is unchanged + return outputState(); + } + + if ( + (state.leftThreadStack == EMPTY_THREAD_ROOT && !state.traverseRight) + || (state.rightThreadStack == EMPTY_THREAD_ROOT && state.traverseRight) + ) { + revert("MIPS64: active thread stack is empty"); + } + + state.step += 1; + + setThreadStateFromCalldata(thread); + validateCalldataThreadWitness(state, thread); + + // Search for the first thread blocked by the wakeup call, if wakeup is set + // Don't allow regular execution until we resolved if we have woken up any thread. + if (state.wakeup != sys.FUTEX_EMPTY_ADDR) { + if (state.wakeup == thread.futexAddr) { + // completed wake traversal + // resume execution on woken up thread + state.wakeup = sys.FUTEX_EMPTY_ADDR; + return outputState(); + } else { + bool traversingRight = state.traverseRight; + bool changedDirections = preemptThread(state, thread); + if (traversingRight && changedDirections) { + // then we've completed wake traversal + // resume thread execution + state.wakeup = sys.FUTEX_EMPTY_ADDR; + } + return outputState(); + } + } + + if (thread.exited) { + popThread(state); + return outputState(); + } + + // check if thread is blocked on a futex + if (thread.futexAddr != sys.FUTEX_EMPTY_ADDR) { + // if set, then check futex + // check timeout first + if (state.step > thread.futexTimeoutStep) { + // timeout! Allow execution + return onWaitComplete(thread, true); + } else { + uint64 mem = MIPS64Memory.readMem( + state.memRoot, + thread.futexAddr & arch.ADDRESS_MASK, + MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1) + ); + if (thread.futexVal == mem) { + // still got expected value, continue sleeping, try next thread. + preemptThread(state, thread); + return outputState(); + } else { + // wake thread up, the value at its address changed! + // Userspace can turn thread back to sleep if it was too sporadic. + return onWaitComplete(thread, false); + } + } + } + + if (state.stepsSinceLastContextSwitch >= sys.SCHED_QUANTUM) { + preemptThread(state, thread); + return outputState(); + } + state.stepsSinceLastContextSwitch += 1; + + // instruction fetch + uint256 insnProofOffset = MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 0); + (uint32 insn, uint32 opcode, uint32 fun) = + ins.getInstructionDetails(thread.pc, state.memRoot, insnProofOffset); + + // Handle syscall separately + // syscall (can read and write) + if (opcode == 0 && fun == 0xC) { + return handleSyscall(_localContext); + } + + // Handle RMW (read-modify-write) ops + if (opcode == ins.OP_LOAD_LINKED || opcode == ins.OP_STORE_CONDITIONAL) { + return handleRMWOps(state, thread, insn, opcode); + } + if (opcode == ins.OP_LOAD_LINKED64 || opcode == ins.OP_STORE_CONDITIONAL64) { + return handleRMWOps(state, thread, insn, opcode); + } + + // Exec the rest of the step logic + st.CpuScalars memory cpu = getCpuScalars(thread); + ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({ + cpu: cpu, + registers: thread.registers, + memRoot: state.memRoot, + memProofOffset: MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), + insn: insn, + opcode: opcode, + fun: fun + }); + bool memUpdated; + uint64 effMemAddr; + (state.memRoot, memUpdated, effMemAddr) = ins.execMipsCoreStepLogic(coreStepArgs); + setStateCpuScalars(thread, cpu); + updateCurrentThreadRoot(); + if (memUpdated) { + handleMemoryUpdate(state, effMemAddr); + } + + return outputState(); + } + } + + function handleMemoryUpdate(State memory _state, uint64 _effMemAddr) internal pure { + if (_effMemAddr == (arch.ADDRESS_MASK & _state.llAddress)) { + // Reserved address was modified, clear the reservation + clearLLMemoryReservation(_state); + } + } + + function clearLLMemoryReservation(State memory _state) internal pure { + _state.llReservationStatus = LL_STATUS_NONE; + _state.llAddress = 0; + _state.llOwnerThread = 0; + } + + function handleRMWOps( + State memory _state, + ThreadState memory _thread, + uint32 _insn, + uint32 _opcode + ) + internal + returns (bytes32) + { + unchecked { + uint64 base = _thread.registers[(_insn >> 21) & 0x1F]; + uint32 rtReg = (_insn >> 16) & 0x1F; + uint64 addr = base + ins.signExtendImmediate(_insn); + + // Determine some opcode-specific parameters + uint8 targetStatus = LL_STATUS_ACTIVE_32_BIT; + uint64 byteLength = 4; + if (_opcode == ins.OP_LOAD_LINKED64 || _opcode == ins.OP_STORE_CONDITIONAL64) { + // Use 64-bit params + targetStatus = LL_STATUS_ACTIVE_64_BIT; + byteLength = 8; + } + + uint64 retVal = 0; + uint64 threadId = _thread.threadID; + if (_opcode == ins.OP_LOAD_LINKED || _opcode == ins.OP_LOAD_LINKED64) { + retVal = loadSubWord(_state, addr, byteLength, true); + + _state.llReservationStatus = targetStatus; + _state.llAddress = addr; + _state.llOwnerThread = threadId; + } else if (_opcode == ins.OP_STORE_CONDITIONAL || _opcode == ins.OP_STORE_CONDITIONAL64) { + // Check if our memory reservation is still intact + if ( + _state.llReservationStatus == targetStatus && _state.llOwnerThread == threadId + && _state.llAddress == addr + ) { + // Complete atomic update: set memory and return 1 for success + clearLLMemoryReservation(_state); + + uint64 val = _thread.registers[rtReg]; + storeSubWord(_state, addr, byteLength, val); + + retVal = 1; + } else { + // Atomic update failed, return 0 for failure + retVal = 0; + } + } else { + revert InvalidRMWInstruction(); + } + + st.CpuScalars memory cpu = getCpuScalars(_thread); + ins.handleRd(cpu, _thread.registers, rtReg, retVal, true); + setStateCpuScalars(_thread, cpu); + updateCurrentThreadRoot(); + + return outputState(); + } + } + + /// @notice Loads a subword of byteLength size contained from memory based on the low-order bits of vaddr + /// @param _vaddr The virtual address of the the subword. + /// @param _byteLength The size of the subword. + /// @param _signExtend Whether to sign extend the selected subwrod. + function loadSubWord( + State memory _state, + uint64 _vaddr, + uint64 _byteLength, + bool _signExtend + ) + internal + pure + returns (uint64 val_) + { + uint64 effAddr = _vaddr & arch.ADDRESS_MASK; + uint256 memProofOffset = MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1); + uint64 mem = MIPS64Memory.readMem(_state.memRoot, effAddr, memProofOffset); + val_ = ins.selectSubWord(_vaddr, mem, _byteLength, _signExtend); + } + + /// @notice Stores a word that has been updated by the specified subword at bit positions determined by the virtual + /// address + /// @param _vaddr The virtual address of the subword. + /// @param _byteLength The size of the subword. + /// @param _value The subword that updates _memWord. + function storeSubWord(State memory _state, uint64 _vaddr, uint64 _byteLength, uint64 _value) internal pure { + uint64 effAddr = _vaddr & arch.ADDRESS_MASK; + uint256 memProofOffset = MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1); + uint64 mem = MIPS64Memory.readMem(_state.memRoot, effAddr, memProofOffset); + + uint64 newMemVal = ins.updateSubWord(_vaddr, mem, _byteLength, _value); + _state.memRoot = MIPS64Memory.writeMem(effAddr, memProofOffset, newMemVal); + } + + function handleSyscall(bytes32 _localContext) internal returns (bytes32 out_) { + unchecked { + // Load state from memory offsets to reduce stack pressure + State memory state; + ThreadState memory thread; + assembly { + state := STATE_MEM_OFFSET + thread := TC_MEM_OFFSET + } + + // Load the syscall numbers and args from the registers + (uint64 syscall_no, uint64 a0, uint64 a1, uint64 a2, uint64 a3) = sys.getSyscallArgs(thread.registers); + // Syscalls that are unimplemented but known return with v0=0 and v1=0 + uint64 v0 = 0; + uint64 v1 = 0; + + if (syscall_no == sys.SYS_MMAP) { + (v0, v1, state.heap) = sys.handleSysMmap(a0, a1, state.heap); + } else if (syscall_no == sys.SYS_BRK) { + // brk: Returns a fixed address for the program break at 0x40000000 + v0 = sys.PROGRAM_BREAK; + } else if (syscall_no == sys.SYS_CLONE) { + if (sys.VALID_SYS_CLONE_FLAGS != a0) { + state.exited = true; + state.exitCode = VMStatuses.PANIC.raw(); + return outputState(); + } + v0 = state.nextThreadID; + v1 = 0; + ThreadState memory newThread; + newThread.threadID = state.nextThreadID; + newThread.exitCode = 0; + newThread.exited = false; + newThread.futexAddr = sys.FUTEX_EMPTY_ADDR; + newThread.futexVal = 0; + newThread.futexTimeoutStep = 0; + newThread.pc = thread.nextPC; + newThread.nextPC = thread.nextPC + 4; + newThread.lo = thread.lo; + newThread.hi = thread.hi; + for (uint256 i; i < 32; i++) { + newThread.registers[i] = thread.registers[i]; + } + newThread.registers[29] = a1; // set stack pointer + // the child will perceive a 0 value as returned value instead, and no error + newThread.registers[2] = 0; + newThread.registers[7] = 0; + state.nextThreadID++; + + // Preempt this thread for the new one. But not before updating PCs + st.CpuScalars memory cpu0 = getCpuScalars(thread); + sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1); + setStateCpuScalars(thread, cpu0); + updateCurrentThreadRoot(); + pushThread(state, newThread); + return outputState(); + } else if (syscall_no == sys.SYS_EXIT_GROUP) { + // exit group: Sets the Exited and ExitCode states to true and argument 0. + state.exited = true; + state.exitCode = uint8(a0); + updateCurrentThreadRoot(); + return outputState(); + } else if (syscall_no == sys.SYS_READ) { + sys.SysReadParams memory args = sys.SysReadParams({ + a0: a0, + a1: a1, + a2: a2, + preimageKey: state.preimageKey, + preimageOffset: state.preimageOffset, + localContext: _localContext, + oracle: ORACLE, + proofOffset: MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), + memRoot: state.memRoot + }); + // Encapsulate execution to avoid stack-too-deep error + (v0, v1) = execSysRead(state, args); + } else if (syscall_no == sys.SYS_WRITE) { + sys.SysWriteParams memory args = sys.SysWriteParams({ + _a0: a0, + _a1: a1, + _a2: a2, + _preimageKey: state.preimageKey, + _preimageOffset: state.preimageOffset, + _proofOffset: MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), + _memRoot: state.memRoot + }); + (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite(args); + } else if (syscall_no == sys.SYS_FCNTL) { + (v0, v1) = sys.handleSysFcntl(a0, a1); + } else if (syscall_no == sys.SYS_GETTID) { + v0 = thread.threadID; + v1 = 0; + } else if (syscall_no == sys.SYS_EXIT) { + thread.exited = true; + thread.exitCode = uint8(a0); + if (lastThreadRemaining(state)) { + state.exited = true; + state.exitCode = uint8(a0); + } + updateCurrentThreadRoot(); + return outputState(); + } else if (syscall_no == sys.SYS_FUTEX) { + // args: a0 = addr, a1 = op, a2 = val, a3 = timeout + uint64 effAddr = a0 & arch.ADDRESS_MASK; + if (a1 == sys.FUTEX_WAIT_PRIVATE) { + uint64 mem = MIPS64Memory.readMem( + state.memRoot, effAddr, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1) + ); + if (mem != a2) { + v0 = sys.SYS_ERROR_SIGNAL; + v1 = sys.EAGAIN; + } else { + thread.futexAddr = effAddr; + thread.futexVal = a2; + thread.futexTimeoutStep = a3 == 0 ? sys.FUTEX_NO_TIMEOUT : state.step + sys.FUTEX_TIMEOUT_STEPS; + // Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete` + updateCurrentThreadRoot(); + return outputState(); + } + } else if (a1 == sys.FUTEX_WAKE_PRIVATE) { + // Trigger thread traversal starting from the left stack until we find one waiting on the wakeup + // address + state.wakeup = effAddr; + // Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees. + // The woken up thread should indicate this in userspace. + v0 = 0; + v1 = 0; + st.CpuScalars memory cpu0 = getCpuScalars(thread); + sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1); + setStateCpuScalars(thread, cpu0); + preemptThread(state, thread); + state.traverseRight = state.leftThreadStack == EMPTY_THREAD_ROOT; + return outputState(); + } else { + v0 = sys.SYS_ERROR_SIGNAL; + v1 = sys.EINVAL; + } + } else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) { + v0 = 0; + v1 = 0; + st.CpuScalars memory cpu0 = getCpuScalars(thread); + sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1); + setStateCpuScalars(thread, cpu0); + preemptThread(state, thread); + return outputState(); + } else if (syscall_no == sys.SYS_OPEN) { + v0 = sys.SYS_ERROR_SIGNAL; + v1 = sys.EBADF; + } else if (syscall_no == sys.SYS_CLOCKGETTIME) { + if (a0 == sys.CLOCK_GETTIME_REALTIME_FLAG || a0 == sys.CLOCK_GETTIME_MONOTONIC_FLAG) { + v0 = 0; + v1 = 0; + uint64 secs = 0; + uint64 nsecs = 0; + if (a0 == sys.CLOCK_GETTIME_MONOTONIC_FLAG) { + secs = uint64(state.step / sys.HZ); + nsecs = uint64((state.step % sys.HZ) * (1_000_000_000 / sys.HZ)); + } + uint64 effAddr = a1 & arch.ADDRESS_MASK; + // First verify the effAddr path + if ( + !MIPS64Memory.isValidProof( + state.memRoot, effAddr, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1) + ) + ) { + revert InvalidMemoryProof(); + } + // Recompute the new root after updating effAddr + state.memRoot = + MIPS64Memory.writeMem(effAddr, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs); + handleMemoryUpdate(state, effAddr); + // Verify the second memory proof against the newly computed root + if ( + !MIPS64Memory.isValidProof( + state.memRoot, effAddr + 8, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 2) + ) + ) { + revert InvalidSecondMemoryProof(); + } + state.memRoot = + MIPS64Memory.writeMem(effAddr + 8, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs); + handleMemoryUpdate(state, effAddr + 8); + } else { + v0 = sys.SYS_ERROR_SIGNAL; + v1 = sys.EINVAL; + } + } else if (syscall_no == sys.SYS_GETPID) { + v0 = 0; + v1 = 0; + } else if (syscall_no == sys.SYS_MUNMAP) { + // ignored + } else if (syscall_no == sys.SYS_GETAFFINITY) { + // ignored + } else if (syscall_no == sys.SYS_MADVISE) { + // ignored + } else if (syscall_no == sys.SYS_RTSIGPROCMASK) { + // ignored + } else if (syscall_no == sys.SYS_SIGALTSTACK) { + // ignored + } else if (syscall_no == sys.SYS_RTSIGACTION) { + // ignored + } else if (syscall_no == sys.SYS_PRLIMIT64) { + // ignored + } else if (syscall_no == sys.SYS_CLOSE) { + // ignored + } else if (syscall_no == sys.SYS_PREAD64) { + // ignored + } else if (syscall_no == sys.SYS_STAT) { + // ignored + } else if (syscall_no == sys.SYS_FSTAT) { + // ignored + } else if (syscall_no == sys.SYS_OPENAT) { + // ignored + } else if (syscall_no == sys.SYS_READLINK) { + // ignored + } else if (syscall_no == sys.SYS_READLINKAT) { + // ignored + } else if (syscall_no == sys.SYS_IOCTL) { + // ignored + } else if (syscall_no == sys.SYS_EPOLLCREATE1) { + // ignored + } else if (syscall_no == sys.SYS_PIPE2) { + // ignored + } else if (syscall_no == sys.SYS_EPOLLCTL) { + // ignored + } else if (syscall_no == sys.SYS_EPOLLPWAIT) { + // ignored + } else if (syscall_no == sys.SYS_GETRANDOM) { + // ignored + } else if (syscall_no == sys.SYS_UNAME) { + // ignored + } else if (syscall_no == sys.SYS_GETUID) { + // ignored + } else if (syscall_no == sys.SYS_GETGID) { + // ignored + } else if (syscall_no == sys.SYS_MINCORE) { + // ignored + } else if (syscall_no == sys.SYS_TGKILL) { + // ignored + } else if (syscall_no == sys.SYS_SETITIMER) { + // ignored + } else if (syscall_no == sys.SYS_TIMERCREATE) { + // ignored + } else if (syscall_no == sys.SYS_TIMERSETTIME) { + // ignored + } else if (syscall_no == sys.SYS_TIMERDELETE) { + // ignored + } else if (syscall_no == sys.SYS_GETRLIMIT) { + // ignored + } else if (syscall_no == sys.SYS_LSEEK) { + // ignored + } else { + revert("MIPS64: unimplemented syscall"); + } + + st.CpuScalars memory cpu = getCpuScalars(thread); + sys.handleSyscallUpdates(cpu, thread.registers, v0, v1); + setStateCpuScalars(thread, cpu); + + updateCurrentThreadRoot(); + out_ = outputState(); + } + } + + function execSysRead( + State memory _state, + sys.SysReadParams memory _args + ) + internal + view + returns (uint64 v0_, uint64 v1_) + { + bool memUpdated; + uint64 memAddr; + (v0_, v1_, _state.preimageOffset, _state.memRoot, memUpdated, memAddr) = sys.handleSysRead(_args); + if (memUpdated) { + handleMemoryUpdate(_state, memAddr); + } + } + + /// @notice Computes the hash of the MIPS state. + /// @return out_ The hashed MIPS state. + function outputState() internal returns (bytes32 out_) { + uint32 exited; + assembly { + // copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data + function copyMem(from, to, size) -> fromOut, toOut { + mstore(to, mload(add(from, sub(32, size)))) + fromOut := add(from, 32) + toOut := add(to, size) + } + + // From points to the MIPS State + let from := STATE_MEM_OFFSET + + // Copy to the free memory pointer + let start := mload(0x40) + let to := start + + // Copy state to free memory + from, to := copyMem(from, to, 32) // memRoot + from, to := copyMem(from, to, 32) // preimageKey + from, to := copyMem(from, to, 8) // preimageOffset + from, to := copyMem(from, to, 8) // heap + from, to := copyMem(from, to, 1) // llReservationStatus + from, to := copyMem(from, to, 8) // llAddress + from, to := copyMem(from, to, 8) // llOwnerThread + let exitCode := mload(from) + from, to := copyMem(from, to, 1) // exitCode + exited := mload(from) + from, to := copyMem(from, to, 1) // exited + from, to := copyMem(from, to, 8) // step + from, to := copyMem(from, to, 8) // stepsSinceLastContextSwitch + from, to := copyMem(from, to, 8) // wakeup + from, to := copyMem(from, to, 1) // traverseRight + from, to := copyMem(from, to, 32) // leftThreadStack + from, to := copyMem(from, to, 32) // rightThreadStack + from, to := copyMem(from, to, 8) // nextThreadID + + // Clean up end of memory + mstore(to, 0) + + // Log the resulting MIPS state, for debugging + log0(start, sub(to, start)) + + // Determine the VM status + let status := 0 + switch exited + case 1 { + switch exitCode + // VMStatusValid + case 0 { status := 0 } + // VMStatusInvalid + case 1 { status := 1 } + // VMStatusPanic + default { status := 2 } + } + // VMStatusUnfinished + default { status := 3 } + + // Compute the hash of the resulting MIPS state and set the status byte + out_ := keccak256(start, sub(to, start)) + out_ := or(and(not(shl(248, 0xFF)), out_), shl(248, status)) + } + + st.assertExitedIsValid(exited); + } + + /// @notice Updates the current thread stack root via inner thread root in calldata + function updateCurrentThreadRoot() internal pure { + State memory state; + ThreadState memory thread; + assembly { + state := STATE_MEM_OFFSET + thread := TC_MEM_OFFSET + } + bytes32 updatedRoot = computeThreadRoot(loadCalldataInnerThreadRoot(), thread); + if (state.traverseRight) { + state.rightThreadStack = updatedRoot; + } else { + state.leftThreadStack = updatedRoot; + } + } + + /// @notice Completes the FUTEX_WAIT syscall. + function onWaitComplete(ThreadState memory _thread, bool _isTimedOut) internal returns (bytes32 out_) { + // Note: no need to reset State.wakeup. If we're here, the wakeup field has already been reset + // Clear the futex state + _thread.futexAddr = sys.FUTEX_EMPTY_ADDR; + _thread.futexVal = 0; + _thread.futexTimeoutStep = 0; + + // Complete the FUTEX_WAIT syscall + uint64 v0 = _isTimedOut ? sys.SYS_ERROR_SIGNAL : 0; + // set errno + uint64 v1 = _isTimedOut ? sys.ETIMEDOUT : 0; + st.CpuScalars memory cpu = getCpuScalars(_thread); + sys.handleSyscallUpdates(cpu, _thread.registers, v0, v1); + setStateCpuScalars(_thread, cpu); + + updateCurrentThreadRoot(); + out_ = outputState(); + } + + /// @notice Preempts the current thread for another and updates the VM state. + /// It reads the inner thread root from calldata to update the current thread stack root. + function preemptThread( + State memory _state, + ThreadState memory _thread + ) + internal + pure + returns (bool changedDirections_) + { + // pop thread from the current stack and push to the other stack + if (_state.traverseRight) { + require(_state.rightThreadStack != EMPTY_THREAD_ROOT, "MIPS64: empty right thread stack"); + _state.rightThreadStack = loadCalldataInnerThreadRoot(); + _state.leftThreadStack = computeThreadRoot(_state.leftThreadStack, _thread); + } else { + require(_state.leftThreadStack != EMPTY_THREAD_ROOT, "MIPS64: empty left thread stack"); + _state.leftThreadStack = loadCalldataInnerThreadRoot(); + _state.rightThreadStack = computeThreadRoot(_state.rightThreadStack, _thread); + } + bytes32 current = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack; + if (current == EMPTY_THREAD_ROOT) { + _state.traverseRight = !_state.traverseRight; + changedDirections_ = true; + } + _state.stepsSinceLastContextSwitch = 0; + } + + /// @notice Pushes a thread to the current thread stack. + function pushThread(State memory _state, ThreadState memory _thread) internal pure { + if (_state.traverseRight) { + _state.rightThreadStack = computeThreadRoot(_state.rightThreadStack, _thread); + } else { + _state.leftThreadStack = computeThreadRoot(_state.leftThreadStack, _thread); + } + _state.stepsSinceLastContextSwitch = 0; + } + + /// @notice Removes the current thread from the stack. + function popThread(State memory _state) internal pure { + if (_state.traverseRight) { + _state.rightThreadStack = loadCalldataInnerThreadRoot(); + } else { + _state.leftThreadStack = loadCalldataInnerThreadRoot(); + } + bytes32 current = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack; + if (current == EMPTY_THREAD_ROOT) { + _state.traverseRight = !_state.traverseRight; + } + _state.stepsSinceLastContextSwitch = 0; + } + + /// @notice Returns true if the number of threads is 1 + function lastThreadRemaining(State memory _state) internal pure returns (bool out_) { + bytes32 inactiveStack = _state.traverseRight ? _state.leftThreadStack : _state.rightThreadStack; + bool currentStackIsAlmostEmpty = loadCalldataInnerThreadRoot() == EMPTY_THREAD_ROOT; + return inactiveStack == EMPTY_THREAD_ROOT && currentStackIsAlmostEmpty; + } + + function computeThreadRoot(bytes32 _currentRoot, ThreadState memory _thread) internal pure returns (bytes32 out_) { + // w_i = hash(w_0 ++ hash(thread)) + bytes32 threadRoot = outputThreadState(_thread); + out_ = keccak256(abi.encodePacked(_currentRoot, threadRoot)); + } + + function outputThreadState(ThreadState memory _thread) internal pure returns (bytes32 out_) { + assembly { + // copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data + function copyMem(from, to, size) -> fromOut, toOut { + mstore(to, mload(add(from, sub(32, size)))) + fromOut := add(from, 32) + toOut := add(to, size) + } + + // From points to the ThreadState + let from := _thread + + // Copy to the free memory pointer + let start := mload(0x40) + let to := start + + // Copy state to free memory + from, to := copyMem(from, to, 8) // threadID + from, to := copyMem(from, to, 1) // exitCode + from, to := copyMem(from, to, 1) // exited + from, to := copyMem(from, to, 8) // futexAddr + from, to := copyMem(from, to, 8) // futexVal + from, to := copyMem(from, to, 8) // futexTimeoutStep + from, to := copyMem(from, to, 8) // pc + from, to := copyMem(from, to, 8) // nextPC + from, to := copyMem(from, to, 8) // lo + from, to := copyMem(from, to, 8) // hi + from := mload(from) // offset to registers + // Copy registers + for { let i := 0 } lt(i, 32) { i := add(i, 1) } { from, to := copyMem(from, to, 8) } + + // Clean up end of memory + mstore(to, 0) + + // Compute the hash of the resulting ThreadState + out_ := keccak256(start, sub(to, start)) + } + } + + function getCpuScalars(ThreadState memory _tc) internal pure returns (st.CpuScalars memory cpu_) { + cpu_ = st.CpuScalars({ pc: _tc.pc, nextPC: _tc.nextPC, lo: _tc.lo, hi: _tc.hi }); + } + + function setStateCpuScalars(ThreadState memory _tc, st.CpuScalars memory _cpu) internal pure { + _tc.pc = _cpu.pc; + _tc.nextPC = _cpu.nextPC; + _tc.lo = _cpu.lo; + _tc.hi = _cpu.hi; + } + + /// @notice Validates the thread witness in calldata against the current thread. + function validateCalldataThreadWitness(State memory _state, ThreadState memory _thread) internal pure { + bytes32 witnessRoot = computeThreadRoot(loadCalldataInnerThreadRoot(), _thread); + bytes32 expectedRoot = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack; + require(expectedRoot == witnessRoot, "MIPS64: invalid thread witness"); + } + + /// @notice Sets the thread context from calldata. + function setThreadStateFromCalldata(ThreadState memory _thread) internal pure { + uint256 s = 0; + assembly { + s := calldatasize() + } + // verify we have enough calldata + require( + s >= (THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE), "MIPS64: insufficient calldata for thread witness" + ); + + unchecked { + assembly { + function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut { + // calldata is packed, thus starting left-aligned, shift-right to pad and right-align + let w := shr(shl(3, sub(32, size)), calldataload(callOffset)) + mstore(memOffset, w) + callOffsetOut := add(callOffset, size) + memOffsetOut := add(memOffset, 32) + } + + let c := THREAD_PROOF_OFFSET + let m := _thread + c, m := putField(c, m, 8) // threadID + c, m := putField(c, m, 1) // exitCode + c, m := putField(c, m, 1) // exited + c, m := putField(c, m, 8) // futexAddr + c, m := putField(c, m, 8) // futexVal + c, m := putField(c, m, 8) // futexTimeoutStep + c, m := putField(c, m, 8) // pc + c, m := putField(c, m, 8) // nextPC + c, m := putField(c, m, 8) // lo + c, m := putField(c, m, 8) // hi + m := mload(m) // offset to registers + // Unpack register calldata into memory + for { let i := 0 } lt(i, 32) { i := add(i, 1) } { c, m := putField(c, m, 8) } + } + } + } + + /// @notice Loads the inner root for the current thread hash onion from calldata. + function loadCalldataInnerThreadRoot() internal pure returns (bytes32 innerThreadRoot_) { + uint256 s = 0; + assembly { + s := calldatasize() + innerThreadRoot_ := calldataload(add(THREAD_PROOF_OFFSET, PACKED_THREAD_STATE_SIZE)) + } + // verify we have enough calldata + require( + s >= (THREAD_PROOF_OFFSET + (PACKED_THREAD_STATE_SIZE + 32)), + "MIPS64: insufficient calldata for thread witness" + ); + } +} diff --git a/packages/contracts-bedrock/src/cannon/PreimageOracle.sol b/packages/contracts-bedrock/src/cannon/PreimageOracle.sol index 4dea97fc68b1b..b45a4ac4edd21 100644 --- a/packages/contracts-bedrock/src/cannon/PreimageOracle.sol +++ b/packages/contracts-bedrock/src/cannon/PreimageOracle.sol @@ -4,11 +4,29 @@ pragma solidity 0.8.15; // Libraries import { LibKeccak } from "@lib-keccak/LibKeccak.sol"; import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; -import "src/cannon/libraries/CannonErrors.sol"; -import "src/cannon/libraries/CannonTypes.sol"; +import { + PartOffsetOOB, + NotEnoughGas, + InvalidProof, + InvalidPreimage, + InvalidInputSize, + WrongStartingBlock, + StatesNotContiguous, + PostStateMatches, + TreeSizeOverflow, + AlreadyFinalized, + ActiveProposal, + BadProposal, + NotInitialized, + AlreadyInitialized, + NotEOA, + InsufficientBond, + BondTransferFailed +} from "src/cannon/libraries/CannonErrors.sol"; +import { LPPMetaData } from "src/cannon/libraries/CannonTypes.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title PreimageOracle /// @notice A contract for storing permissioned pre-images. @@ -33,8 +51,8 @@ contract PreimageOracle is ISemver { uint256 public constant PRECOMPILE_CALL_RESERVED_GAS = 100_000; /// @notice The semantic version of the Preimage Oracle contract. - /// @custom:semver 1.1.3-beta.5 - string public constant version = "1.1.3-beta.5"; + /// @custom:semver 1.1.3-beta.8 + string public constant version = "1.1.3-beta.8"; //////////////////////////////////////////////////////////////// // Authorized Preimage Parts // diff --git a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS2.sol b/packages/contracts-bedrock/src/cannon/interfaces/IMIPS2.sol deleted file mode 100644 index 8f57c0f636952..0000000000000 --- a/packages/contracts-bedrock/src/cannon/interfaces/IMIPS2.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; - -/// @title IMIPS2 -/// @notice Interface for the MIPS2 contract. -interface IMIPS2 is ISemver { - error InvalidExitedValue(); - error InvalidMemoryProof(); - error InvalidSecondMemoryProof(); - error InvalidRMWInstruction(); - - function oracle() external view returns (IPreimageOracle oracle_); - function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); - - function __constructor__(IPreimageOracle _oracle) external; -} diff --git a/packages/contracts-bedrock/src/cannon/interfaces/IPreimageOracle.sol b/packages/contracts-bedrock/src/cannon/interfaces/IPreimageOracle.sol deleted file mode 100644 index 4a885d3dd03bf..0000000000000 --- a/packages/contracts-bedrock/src/cannon/interfaces/IPreimageOracle.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title IPreimageOracle -/// @notice Interface for a preimage oracle. -interface IPreimageOracle { - /// @notice Returns the length of the large preimage proposal challenge period. - /// @return challengePeriod_ The length of the challenge period in seconds. - function challengePeriod() external view returns (uint256 challengePeriod_); - - /// @notice Reads a preimage from the oracle. - /// @param _key The key of the preimage to read. - /// @param _offset The offset of the preimage to read. - /// @return dat_ The preimage data. - /// @return datLen_ The length of the preimage data. - function readPreimage(bytes32 _key, uint256 _offset) external view returns (bytes32 dat_, uint256 datLen_); - - /// @notice Loads of local data part into the preimage oracle. - /// @param _ident The identifier of the local data. - /// @param _localContext The local key context for the preimage oracle. Optionally, can be set as a constant - /// if the caller only requires one set of local keys. - /// @param _word The local data word. - /// @param _size The number of bytes in `_word` to load. - /// @param _partOffset The offset of the local data part to write to the oracle. - /// @dev The local data parts are loaded into the preimage oracle under the context - /// of the caller - no other account can write to the caller's context - /// specific data. - /// - /// There are 5 local data identifiers: - /// ┌────────────┬────────────────────────┐ - /// │ Identifier │ Data │ - /// ├────────────┼────────────────────────┤ - /// │ 1 │ L1 Head Hash (bytes32) │ - /// │ 2 │ Output Root (bytes32) │ - /// │ 3 │ Root Claim (bytes32) │ - /// │ 4 │ L2 Block Number (u64) │ - /// │ 5 │ Chain ID (u64) │ - /// └────────────┴────────────────────────┘ - function loadLocalData( - uint256 _ident, - bytes32 _localContext, - bytes32 _word, - uint256 _size, - uint256 _partOffset - ) - external - returns (bytes32 key_); - - /// @notice Prepares a preimage to be read by keccak256 key, starting at the given offset and up to 32 bytes - /// (clipped at preimage length, if out of data). - /// @param _partOffset The offset of the preimage to read. - /// @param _preimage The preimage data. - function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external; - - /// @notice Prepares a preimage to be read by sha256 key, starting at the given offset and up to 32 bytes - /// (clipped at preimage length, if out of data). - /// @param _partOffset The offset of the preimage to read. - /// @param _preimage The preimage data. - function loadSha256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external; - - /// @notice Verifies that `p(_z) = _y` given `_commitment` that corresponds to the polynomial `p(x)` and a KZG - // proof. The value `y` is the pre-image, and the preimage key is `5 ++ keccak256(_commitment ++ z)[1:]`. - /// @param _z Big endian point value. Part of the preimage key. - /// @param _y Big endian point value. The preimage for the key. - /// @param _commitment The commitment to the polynomial. 48 bytes, part of the preimage key. - /// @param _proof The KZG proof, part of the preimage key. - /// @param _partOffset The offset of the preimage to store. - function loadBlobPreimagePart( - uint256 _z, - uint256 _y, - bytes calldata _commitment, - bytes calldata _proof, - uint256 _partOffset - ) - external; - - /// @notice Prepares a precompile result to be read by a precompile key for the specified offset. - /// The precompile result data is a concatenation of the precompile call status byte and its return data. - /// The preimage key is `6 ++ keccak256(precompile ++ input)[1:]`. - /// @param _partOffset The offset of the precompile result being loaded. - /// @param _precompile The precompile address - /// @param _requiredGas The gas required to fully execute an L1 precompile. - /// @param _input The input to the precompile call. - function loadPrecompilePreimagePart( - uint256 _partOffset, - address _precompile, - uint64 _requiredGas, - bytes calldata _input - ) - external; - - /// @notice Returns the minimum size (in bytes) of a large preimage proposal. - function minProposalSize() external view returns (uint256); - - function __constructor__(uint256 _minProposalSize, uint256 _challengePeriod) external; -} diff --git a/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol b/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol index 3649852cec6f2..dd0e78a3a33a1 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/CannonErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; /// @notice Thrown when a passed part offset is out of bounds. error PartOffsetOOB(); diff --git a/packages/contracts-bedrock/src/cannon/libraries/CannonTypes.sol b/packages/contracts-bedrock/src/cannon/libraries/CannonTypes.sol index 2e7c50ed862d1..26d0a17edae4d 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/CannonTypes.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/CannonTypes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; using LPPMetadataLib for LPPMetaData global; diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Arch.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Arch.sol new file mode 100644 index 0000000000000..a1d689e731dba --- /dev/null +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Arch.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library MIPS64Arch { + uint64 internal constant WORD_SIZE = 64; + uint64 internal constant WORD_SIZE_BYTES = 8; + uint64 internal constant EXT_MASK = 0x7; + uint64 internal constant ADDRESS_MASK = 0xFFFFFFFFFFFFFFF8; +} diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol new file mode 100644 index 0000000000000..6191cdfbe0a47 --- /dev/null +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol @@ -0,0 +1,921 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Libraries +import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; +import { MIPS64State as st } from "src/cannon/libraries/MIPS64State.sol"; +import { MIPS64Arch as arch } from "src/cannon/libraries/MIPS64Arch.sol"; + +library MIPS64Instructions { + uint32 internal constant OP_LOAD_LINKED = 0x30; + uint32 internal constant OP_STORE_CONDITIONAL = 0x38; + uint32 internal constant OP_LOAD_LINKED64 = 0x34; + uint32 internal constant OP_STORE_CONDITIONAL64 = 0x3C; + uint32 internal constant OP_LOAD_DOUBLE_LEFT = 0x1A; + uint32 internal constant OP_LOAD_DOUBLE_RIGHT = 0x1B; + uint32 internal constant REG_RA = 31; + uint64 internal constant U64_MASK = 0xFFFFFFFFFFFFFFFF; + uint32 internal constant U32_MASK = 0xFFffFFff; + + error InvalidPC(); + + struct CoreStepLogicParams { + /// @param opcode The opcode value parsed from insn_. + st.CpuScalars cpu; + /// @param registers The CPU registers. + uint64[32] registers; + /// @param memRoot The current merkle root of the memory. + bytes32 memRoot; + /// @param memProofOffset The offset in calldata specify where the memory merkle proof is located. + uint256 memProofOffset; + /// @param insn The current 32-bit instruction at the pc. + uint32 insn; + /// @param cpu The CPU scalar fields. + uint32 opcode; + /// @param fun The function value parsed from insn_. + uint32 fun; + } + + /// @param _pc The program counter. + /// @param _memRoot The current memory root. + /// @param _insnProofOffset The calldata offset of the memory proof for the current instruction. + /// @return insn_ The current 32-bit instruction at the pc. + /// @return opcode_ The opcode value parsed from insn_. + /// @return fun_ The function value parsed from insn_. + function getInstructionDetails( + uint64 _pc, + bytes32 _memRoot, + uint256 _insnProofOffset + ) + internal + pure + returns (uint32 insn_, uint32 opcode_, uint32 fun_) + { + unchecked { + if (_pc & 0x3 != 0) { + revert InvalidPC(); + } + uint64 word = MIPS64Memory.readMem(_memRoot, _pc & arch.ADDRESS_MASK, _insnProofOffset); + insn_ = uint32(selectSubWord(_pc, word, 4, false)); + opcode_ = insn_ >> 26; // First 6-bits + fun_ = insn_ & 0x3f; // Last 6-bits + + return (insn_, opcode_, fun_); + } + } + + /// @notice Execute core MIPS step logic. + /// @return newMemRoot_ The updated merkle root of memory after any modifications, may be unchanged. + /// @return memUpdated_ True if memory was modified. + /// @return effMemAddr_ Holds the effective address that was updated if memUpdated_ is true. + function execMipsCoreStepLogic(CoreStepLogicParams memory _args) + internal + pure + returns (bytes32 newMemRoot_, bool memUpdated_, uint64 effMemAddr_) + { + unchecked { + newMemRoot_ = _args.memRoot; + memUpdated_ = false; + effMemAddr_ = 0; + + // j-type j/jal + if (_args.opcode == 2 || _args.opcode == 3) { + // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset + uint64 target = (_args.cpu.nextPC & signExtend(0xF0000000, 32)) | uint64((_args.insn & 0x03FFFFFF) << 2); + handleJump(_args.cpu, _args.registers, _args.opcode == 2 ? 0 : REG_RA, target); + return (newMemRoot_, memUpdated_, effMemAddr_); + } + + // register fetch + uint64 rs = 0; // source register 1 value + uint64 rt = 0; // source register 2 / temp value + uint64 rtReg = uint64((_args.insn >> 16) & 0x1F); + + // R-type or I-type (stores rt) + rs = _args.registers[(_args.insn >> 21) & 0x1F]; + uint64 rdReg = rtReg; + + // 64-bit opcodes lwu, ldl, ldr + if (_args.opcode == 0x27 || _args.opcode == 0x1A || _args.opcode == 0x1B) { + rt = _args.registers[rtReg]; + rdReg = rtReg; + } else if (_args.opcode == 0 || _args.opcode == 0x1c) { + // R-type (stores rd) + rt = _args.registers[rtReg]; + rdReg = uint64((_args.insn >> 11) & 0x1F); + } else if (_args.opcode < 0x20) { + // rt is SignExtImm + // don't sign extend for andi, ori, xori + if (_args.opcode == 0xC || _args.opcode == 0xD || _args.opcode == 0xe) { + // ZeroExtImm + rt = uint64(_args.insn & 0xFFFF); + } else { + // SignExtImm + rt = signExtendImmediate(_args.insn); + } + } else if (_args.opcode >= 0x28 || _args.opcode == 0x22 || _args.opcode == 0x26) { + // store rt value with store + rt = _args.registers[rtReg]; + + // store actual rt with lwl and lwr + rdReg = rtReg; + } + + if ((_args.opcode >= 4 && _args.opcode < 8) || _args.opcode == 1) { + handleBranch({ + _cpu: _args.cpu, + _registers: _args.registers, + _opcode: _args.opcode, + _insn: _args.insn, + _rtReg: rtReg, + _rs: rs + }); + return (newMemRoot_, memUpdated_, effMemAddr_); + } + + uint64 storeAddr = U64_MASK; + // memory fetch (all I-type) + // we do the load for stores also + uint64 mem = 0; + if (_args.opcode >= 0x20 || _args.opcode == OP_LOAD_DOUBLE_LEFT || _args.opcode == OP_LOAD_DOUBLE_RIGHT) { + // M[R[rs]+SignExtImm] + rs += signExtendImmediate(_args.insn); + uint64 addr = rs & arch.ADDRESS_MASK; + mem = MIPS64Memory.readMem(_args.memRoot, addr, _args.memProofOffset); + if (_args.opcode >= 0x28) { + // store for 32-bit + // for 64-bit: ld (0x37) is the only non-store opcode >= 0x28 + if (_args.opcode != 0x37) { + // store + storeAddr = addr; + // store opcodes don't write back to a register + rdReg = 0; + } + } + } + + // ALU + // Note: swr outputs more than 8 bytes without the u64_mask + uint64 val = executeMipsInstruction(_args.insn, _args.opcode, _args.fun, rs, rt, mem) & U64_MASK; + + uint64 funSel = 0x20; + if (_args.opcode == 0 && _args.fun >= 8 && _args.fun < funSel) { + if (_args.fun == 8 || _args.fun == 9) { + // jr/jalr + handleJump(_args.cpu, _args.registers, _args.fun == 8 ? 0 : rdReg, rs); + return (newMemRoot_, memUpdated_, effMemAddr_); + } + + if (_args.fun == 0xa) { + // movz + handleRd(_args.cpu, _args.registers, rdReg, rs, rt == 0); + return (newMemRoot_, memUpdated_, effMemAddr_); + } + if (_args.fun == 0xb) { + // movn + handleRd(_args.cpu, _args.registers, rdReg, rs, rt != 0); + return (newMemRoot_, memUpdated_, effMemAddr_); + } + + // lo and hi registers + // can write back + if (_args.fun >= 0x10 && _args.fun < funSel) { + handleHiLo({ + _cpu: _args.cpu, + _registers: _args.registers, + _fun: _args.fun, + _rs: rs, + _rt: rt, + _storeReg: rdReg + }); + return (newMemRoot_, memUpdated_, effMemAddr_); + } + } + + // write memory + if (storeAddr != U64_MASK) { + newMemRoot_ = MIPS64Memory.writeMem(storeAddr, _args.memProofOffset, val); + memUpdated_ = true; + effMemAddr_ = storeAddr; + } + + // write back the value to destination register + handleRd(_args.cpu, _args.registers, rdReg, val, true); + + return (newMemRoot_, memUpdated_, effMemAddr_); + } + } + + function signExtendImmediate(uint32 _insn) internal pure returns (uint64 offset_) { + unchecked { + return signExtend(_insn & 0xFFFF, 16); + } + } + + /// @notice Execute an instruction. + function executeMipsInstruction( + uint32 _insn, + uint32 _opcode, + uint32 _fun, + uint64 _rs, + uint64 _rt, + uint64 _mem + ) + internal + pure + returns (uint64 out_) + { + unchecked { + if (_opcode == 0 || (_opcode >= 8 && _opcode < 0xF) || _opcode == 0x18 || _opcode == 0x19) { + assembly { + // transform ArithLogI to SPECIAL + switch _opcode + // addi + case 0x8 { _fun := 0x20 } + // addiu + case 0x9 { _fun := 0x21 } + // stli + case 0xA { _fun := 0x2A } + // sltiu + case 0xB { _fun := 0x2B } + // andi + case 0xC { _fun := 0x24 } + // ori + case 0xD { _fun := 0x25 } + // xori + case 0xE { _fun := 0x26 } + // daddi + case 0x18 { _fun := 0x2C } + // daddiu + case 0x19 { _fun := 0x2D } + } + + // sll + if (_fun == 0x00) { + return signExtend((_rt & U32_MASK) << ((_insn >> 6) & 0x1F), 32); + } + // srl + else if (_fun == 0x02) { + return signExtend((_rt & U32_MASK) >> ((_insn >> 6) & 0x1F), 32); + } + // sra + else if (_fun == 0x03) { + uint32 shamt = (_insn >> 6) & 0x1F; + return signExtend((_rt & U32_MASK) >> shamt, 32 - shamt); + } + // sllv + else if (_fun == 0x04) { + return signExtend((_rt & U32_MASK) << (_rs & 0x1F), 32); + } + // srlv + else if (_fun == 0x6) { + return signExtend((_rt & U32_MASK) >> (_rs & 0x1F), 32); + } + // srav + else if (_fun == 0x07) { + // shamt here is different than the typical shamt which comes from the + // instruction itself, here it comes from the rs register + uint64 shamt = _rs & 0x1F; + return signExtend((_rt & U32_MASK) >> shamt, 32 - shamt); + } + // functs in range [0x8, 0x1b] are handled specially by other functions + // Explicitly enumerate each funct in range to reduce code diff against Go Vm + // jr + else if (_fun == 0x08) { + return _rs; + } + // jalr + else if (_fun == 0x09) { + return _rs; + } + // movz + else if (_fun == 0x0a) { + return _rs; + } + // movn + else if (_fun == 0x0b) { + return _rs; + } + // syscall + else if (_fun == 0x0c) { + return _rs; + } + // 0x0d - break not supported + // sync + else if (_fun == 0x0f) { + return _rs; + } + // mfhi + else if (_fun == 0x10) { + return _rs; + } + // mthi + else if (_fun == 0x11) { + return _rs; + } + // mflo + else if (_fun == 0x12) { + return _rs; + } + // mtlo + else if (_fun == 0x13) { + return _rs; + } + // dsllv + else if (_fun == 0x14) { + return _rt; + } + // dsrlv + else if (_fun == 0x16) { + return _rt; + } + // dsrav + else if (_fun == 0x17) { + return _rt; + } + // mult + else if (_fun == 0x18) { + return _rs; + } + // multu + else if (_fun == 0x19) { + return _rs; + } + // div + else if (_fun == 0x1a) { + return _rs; + } + // divu + else if (_fun == 0x1b) { + return _rs; + } + // dmult + else if (_fun == 0x1c) { + return _rs; + } + // dmultu + else if (_fun == 0x1d) { + return _rs; + } + // ddiv + else if (_fun == 0x1e) { + return _rs; + } + // ddivu + else if (_fun == 0x1f) { + return _rs; + } + // The rest includes transformed R-type arith imm instructions + // add + else if (_fun == 0x20) { + return signExtend(uint64(uint32(_rs) + uint32(_rt)), 32); + } + // addu + else if (_fun == 0x21) { + return signExtend(uint64(uint32(_rs) + uint32(_rt)), 32); + } + // sub + else if (_fun == 0x22) { + return signExtend(uint64(uint32(_rs) - uint32(_rt)), 32); + } + // subu + else if (_fun == 0x23) { + return signExtend(uint64(uint32(_rs) - uint32(_rt)), 32); + } + // and + else if (_fun == 0x24) { + return (_rs & _rt); + } + // or + else if (_fun == 0x25) { + return (_rs | _rt); + } + // xor + else if (_fun == 0x26) { + return (_rs ^ _rt); + } + // nor + else if (_fun == 0x27) { + return ~(_rs | _rt); + } + // slti + else if (_fun == 0x2a) { + return int64(_rs) < int64(_rt) ? 1 : 0; + } + // sltiu + else if (_fun == 0x2b) { + return _rs < _rt ? 1 : 0; + } + // dadd + else if (_fun == 0x2c) { + return (_rs + _rt); + } + // daddu + else if (_fun == 0x2d) { + return (_rs + _rt); + } + // dsub + else if (_fun == 0x2e) { + return (_rs - _rt); + } + // dsubu + else if (_fun == 0x2f) { + return (_rs - _rt); + } + // dsll + else if (_fun == 0x38) { + return _rt << ((_insn >> 6) & 0x1f); + } + // dsrl + else if (_fun == 0x3A) { + return _rt >> ((_insn >> 6) & 0x1f); + } + // dsra + else if (_fun == 0x3B) { + return uint64(int64(_rt) >> ((_insn >> 6) & 0x1f)); + } + // dsll32 + else if (_fun == 0x3c) { + return _rt << (((_insn >> 6) & 0x1f) + 32); + } + // dsrl32 + else if (_fun == 0x3e) { + return _rt >> (((_insn >> 6) & 0x1f) + 32); + } + // dsra32 + else if (_fun == 0x3f) { + return uint64(int64(_rt) >> (((_insn >> 6) & 0x1f) + 32)); + } else { + revert("MIPS64: invalid instruction"); + } + } else { + // SPECIAL2 + if (_opcode == 0x1C) { + // mul + if (_fun == 0x2) { + return signExtend(uint32(int32(uint32(_rs)) * int32(uint32(_rt))), 32); + } + // clz, clo + else if (_fun == 0x20 || _fun == 0x21) { + if (_fun == 0x20) { + _rs = ~_rs; + } + uint32 i = 0; + while (_rs & 0x80000000 != 0) { + i++; + _rs <<= 1; + } + return i; + } + } + // lui + else if (_opcode == 0x0F) { + return signExtend(_rt << 16, 32); + } + // lb + else if (_opcode == 0x20) { + return selectSubWord(_rs, _mem, 1, true); + } + // lh + else if (_opcode == 0x21) { + return selectSubWord(_rs, _mem, 2, true); + } + // lwl + else if (_opcode == 0x22) { + uint32 w = uint32(selectSubWord(_rs, _mem, 4, false)); + uint32 val = w << uint32((_rs & 3) * 8); + uint64 mask = uint64(U32_MASK << uint32((_rs & 3) * 8)); + return signExtend(((_rt & ~mask) | uint64(val)) & U32_MASK, 32); + } + // lw + else if (_opcode == 0x23) { + return selectSubWord(_rs, _mem, 4, true); + } + // lbu + else if (_opcode == 0x24) { + return selectSubWord(_rs, _mem, 1, false); + } + // lhu + else if (_opcode == 0x25) { + return selectSubWord(_rs, _mem, 2, false); + } + // lwr + else if (_opcode == 0x26) { + uint32 w = uint32(selectSubWord(_rs, _mem, 4, false)); + uint32 val = w >> (24 - (_rs & 3) * 8); + uint32 mask = U32_MASK >> (24 - (_rs & 3) * 8); + uint64 lwrResult = (uint32(_rt) & ~mask) | val; + if (_rs & 3 == 3) { + // loaded bit 31 + return signExtend(uint64(lwrResult), 32); + } else { + // NOTE: cannon64 implementation specific: We leave the upper word untouched + uint64 rtMask = 0xFF_FF_FF_FF_00_00_00_00; + return ((_rt & rtMask) | uint64(lwrResult)); + } + } + // sb + else if (_opcode == 0x28) { + return updateSubWord(_rs, _mem, 1, _rt); + } + // sh + else if (_opcode == 0x29) { + return updateSubWord(_rs, _mem, 2, _rt); + } + // swl + else if (_opcode == 0x2a) { + uint64 sr = (_rs & 3) << 3; + uint64 val = ((_rt & U32_MASK) >> sr) << (32 - ((_rs & 0x4) << 3)); + uint64 mask = (uint64(U32_MASK) >> sr) << (32 - ((_rs & 0x4) << 3)); + return (_mem & ~mask) | val; + } + // sw + else if (_opcode == 0x2b) { + return updateSubWord(_rs, _mem, 4, _rt); + } + // swr + else if (_opcode == 0x2e) { + uint32 w = uint32(selectSubWord(_rs, _mem, 4, false)); + uint64 val = _rt << (24 - (_rs & 3) * 8); + uint64 mask = U32_MASK << uint32(24 - (_rs & 3) * 8); + uint64 swrResult = (w & ~mask) | uint32(val); + return updateSubWord(_rs, _mem, 4, swrResult); + } + // MIPS64 + // ldl + else if (_opcode == 0x1a) { + uint64 sl = (_rs & 0x7) << 3; + uint64 val = _mem << sl; + uint64 mask = U64_MASK << sl; + return (val | (_rt & ~mask)); + } + // ldr + else if (_opcode == 0x1b) { + uint64 sr = 56 - ((_rs & 0x7) << 3); + uint64 val = _mem >> sr; + uint64 mask = U64_MASK << (64 - sr); + return (val | (_rt & mask)); + } + // lwu + else if (_opcode == 0x27) { + return ((_mem >> (32 - ((_rs & 0x4) << 3))) & U32_MASK); + } + // sdl + else if (_opcode == 0x2c) { + uint64 sr = (_rs & 0x7) << 3; + uint64 val = _rt >> sr; + uint64 mask = U64_MASK >> sr; + return (val | (_mem & ~mask)); + } + // sdr + else if (_opcode == 0x2d) { + uint64 sl = 56 - ((_rs & 0x7) << 3); + uint64 val = _rt << sl; + uint64 mask = U64_MASK << sl; + return (val | (_mem & ~mask)); + } + // ld + else if (_opcode == 0x37) { + return _mem; + } + // sd + else if (_opcode == 0x3F) { + return _rt; + } else { + revert("MIPS64: invalid instruction"); + } + } + revert("MIPS64: invalid instruction"); + } + } + + /// @notice Extends the value leftwards with its most significant bit (sign extension). + function signExtend(uint64 _dat, uint64 _idx) internal pure returns (uint64 out_) { + unchecked { + bool isSigned = (_dat >> (_idx - 1)) != 0; + uint256 signed = ((1 << (arch.WORD_SIZE - _idx)) - 1) << _idx; + uint256 mask = (1 << _idx) - 1; + return uint64(_dat & mask | (isSigned ? signed : 0)); + } + } + + /// @notice Handles a branch instruction, updating the MIPS state PC where needed. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _opcode The opcode of the branch instruction. + /// @param _insn The instruction to be executed. + /// @param _rtReg The register to be used for the branch. + /// @param _rs The register to be compared with the branch register. + function handleBranch( + st.CpuScalars memory _cpu, + uint64[32] memory _registers, + uint32 _opcode, + uint32 _insn, + uint64 _rtReg, + uint64 _rs + ) + internal + pure + { + unchecked { + bool shouldBranch = false; + + if (_cpu.nextPC != _cpu.pc + 4) { + revert("MIPS64: branch in delay slot"); + } + + // beq/bne: Branch on equal / not equal + if (_opcode == 4 || _opcode == 5) { + uint64 rt = _registers[_rtReg]; + shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5); + } + // blez: Branches if instruction is less than or equal to zero + else if (_opcode == 6) { + shouldBranch = int64(_rs) <= 0; + } + // bgtz: Branches if instruction is greater than zero + else if (_opcode == 7) { + shouldBranch = int64(_rs) > 0; + } + // bltz/bgez: Branch on less than zero / greater than or equal to zero + else if (_opcode == 1) { + // regimm + uint32 rtv = ((_insn >> 16) & 0x1F); + if (rtv == 0) { + shouldBranch = int64(_rs) < 0; + } + if (rtv == 1) { + shouldBranch = int64(_rs) >= 0; + } + // bgezal (i.e. bal mnemonic) + if (rtv == 0x11) { + shouldBranch = int64(_rs) >= 0; + _registers[REG_RA] = _cpu.pc + 8; // always set regardless of branch taken + } + } + + // Update the state's previous PC + uint64 prevPC = _cpu.pc; + + // Execute the delay slot first + _cpu.pc = _cpu.nextPC; + + // If we should branch, update the PC to the branch target + // Otherwise, proceed to the next instruction + if (shouldBranch) { + _cpu.nextPC = prevPC + 4 + (signExtend(_insn & 0xFFFF, 16) << 2); + } else { + _cpu.nextPC = _cpu.nextPC + 4; + } + } + } + + /// @notice Handles HI and LO register instructions. It also additionally handles doubleword variable shift + /// operations + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _fun The function code of the instruction. + /// @param _rs The value of the RS register. + /// @param _rt The value of the RT register. + /// @param _storeReg The register to store the result in. + function handleHiLo( + st.CpuScalars memory _cpu, + uint64[32] memory _registers, + uint32 _fun, + uint64 _rs, + uint64 _rt, + uint64 _storeReg + ) + internal + pure + { + unchecked { + uint64 val = 0; + + // mfhi: Move the contents of the HI register into the destination + if (_fun == 0x10) { + val = _cpu.hi; + } + // mthi: Move the contents of the source into the HI register + else if (_fun == 0x11) { + _cpu.hi = _rs; + } + // mflo: Move the contents of the LO register into the destination + else if (_fun == 0x12) { + val = _cpu.lo; + } + // mtlo: Move the contents of the source into the LO register + else if (_fun == 0x13) { + _cpu.lo = _rs; + } + // mult: Multiplies `rs` by `rt` and stores the result in HI and LO registers + else if (_fun == 0x18) { + uint64 acc = uint64(int64(int32(uint32(_rs))) * int64(int32(uint32(_rt)))); + _cpu.hi = signExtend(uint64(acc >> 32), 32); + _cpu.lo = signExtend(uint64(uint32(acc)), 32); + } + // multu: Unsigned multiplies `rs` by `rt` and stores the result in HI and LO registers + else if (_fun == 0x19) { + uint64 acc = uint64(uint32(_rs)) * uint64(uint32(_rt)); + _cpu.hi = signExtend(uint64(acc >> 32), 32); + _cpu.lo = signExtend(uint64(uint32(acc)), 32); + } + // div: Divides `rs` by `rt`. + // Stores the quotient in LO + // And the remainder in HI + else if (_fun == 0x1a) { + if (uint32(_rt) == 0) { + revert("MIPS64: division by zero"); + } + _cpu.hi = signExtend(uint32(int32(uint32(_rs)) % int32(uint32(_rt))), 32); + _cpu.lo = signExtend(uint32(int32(uint32(_rs)) / int32(uint32(_rt))), 32); + } + // divu: Unsigned divides `rs` by `rt`. + // Stores the quotient in LO + // And the remainder in HI + else if (_fun == 0x1b) { + if (uint32(_rt) == 0) { + revert("MIPS64: division by zero"); + } + _cpu.hi = signExtend(uint64(uint32(_rs) % uint32(_rt)), 32); + _cpu.lo = signExtend(uint64(uint32(_rs) / uint32(_rt)), 32); + } + // dsllv + else if (_fun == 0x14) { + val = _rt << (_rs & 0x3F); + } + // dsrlv + else if (_fun == 0x16) { + val = _rt >> (_rs & 0x3F); + } + // dsrav + else if (_fun == 0x17) { + val = uint64(int64(_rt) >> (_rs & 0x3F)); + } + // dmult + else if (_fun == 0x1c) { + int128 res = int128(int64(_rs)) * int128(int64(_rt)); + _cpu.hi = uint64(int64(res >> 64)); + _cpu.lo = uint64(uint128(res) & U64_MASK); + } + // dmultu + else if (_fun == 0x1d) { + uint128 res = uint128(_rs) * uint128(_rt); + _cpu.hi = uint64(res >> 64); + _cpu.lo = uint64(res); + } + // ddiv + else if (_fun == 0x1e) { + if (_rt == 0) { + revert("MIPS64: division by zero"); + } + _cpu.hi = uint64(int64(_rs) % int64(_rt)); + _cpu.lo = uint64(int64(_rs) / int64(_rt)); + } + // ddivu + else if (_fun == 0x1f) { + if (_rt == 0) { + revert("MIPS64: division by zero"); + } + _cpu.hi = _rs % _rt; + _cpu.lo = _rs / _rt; + } + + // Store the result in the destination register, if applicable + if (_storeReg != 0) { + _registers[_storeReg] = val; + } + + // Update the PC + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } + } + + /// @notice Handles a jump instruction, updating the MIPS state PC where needed. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _linkReg The register to store the link to the instruction after the delay slot instruction. + /// @param _dest The destination to jump to. + function handleJump( + st.CpuScalars memory _cpu, + uint64[32] memory _registers, + uint64 _linkReg, + uint64 _dest + ) + internal + pure + { + unchecked { + if (_cpu.nextPC != _cpu.pc + 4) { + revert("MIPS64: jump in delay slot"); + } + + // Update the next PC to the jump destination. + uint64 prevPC = _cpu.pc; + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _dest; + + // Update the link-register to the instruction after the delay slot instruction. + if (_linkReg != 0) { + _registers[_linkReg] = prevPC + 8; + } + } + } + + /// @notice Handles a storing a value into a register. + /// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo. + /// @param _registers Holds the current state of the cpu registers. + /// @param _storeReg The register to store the value into. + /// @param _val The value to store. + /// @param _conditional Whether or not the store is conditional. + function handleRd( + st.CpuScalars memory _cpu, + uint64[32] memory _registers, + uint64 _storeReg, + uint64 _val, + bool _conditional + ) + internal + pure + { + unchecked { + // The destination register must be valid. + require(_storeReg < 32, "MIPS64: valid register"); + + // Never write to reg 0, and it can be conditional (movz, movn). + if (_storeReg != 0 && _conditional) { + _registers[_storeReg] = _val; + } + + // Update the PC. + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } + } + + /// @notice Selects a subword of byteLength size contained in memWord based on the low-order bits of vaddr + /// @param _vaddr The virtual address of the the subword. + /// @param _memWord The full word to select a subword from. + /// @param _byteLength The size of the subword. + /// @param _signExtend Whether to sign extend the selected subwrod. + function selectSubWord( + uint64 _vaddr, + uint64 _memWord, + uint64 _byteLength, + bool _signExtend + ) + internal + pure + returns (uint64 retval_) + { + (uint64 dataMask, uint64 bitOffset, uint64 bitLength) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); + retval_ = (_memWord >> bitOffset) & dataMask; + if (_signExtend) { + retval_ = signExtend(retval_, bitLength); + } + return retval_; + } + + /// @notice Returns a word that has been updated by the specified subword at bit positions determined by the virtual + /// address + /// @param _vaddr The virtual address of the subword. + /// @param _memWord The full word to update. + /// @param _byteLength The size of the subword. + /// @param _value The subword that updates _memWord. + function updateSubWord( + uint64 _vaddr, + uint64 _memWord, + uint64 _byteLength, + uint64 _value + ) + internal + pure + returns (uint64 word_) + { + (uint64 dataMask, uint64 bitOffset,) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); + uint64 subWordValue = dataMask & _value; + uint64 memUpdateMask = dataMask << bitOffset; + return subWordValue << bitOffset | (~memUpdateMask) & _memWord; + } + + function calculateSubWordMaskAndOffset( + uint64 _vaddr, + uint64 _byteLength + ) + internal + pure + returns (uint64 dataMask_, uint64 bitOffset_, uint64 bitLength_) + { + uint64 bitLength = _byteLength << 3; + uint64 dataMask = ~uint64(0) >> (arch.WORD_SIZE - bitLength); + + // Figure out sub-word index based on the low-order bits in vaddr + uint64 byteIndexMask = _vaddr & arch.EXT_MASK & ~(_byteLength - 1); + uint64 maxByteShift = arch.WORD_SIZE_BYTES - _byteLength; + uint64 byteIndex = _vaddr & byteIndexMask; + uint64 bitOffset = (maxByteShift - byteIndex) << 3; + + return (dataMask, bitOffset, bitLength); + } +} diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Memory.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Memory.sol new file mode 100644 index 0000000000000..2f77fcd599c48 --- /dev/null +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Memory.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Libraries +import { InvalidMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; + +library MIPS64Memory { + uint64 internal constant EXT_MASK = 0x7; + uint64 internal constant MEM_PROOF_LEAF_COUNT = 60; + uint256 internal constant U64_MASK = 0xFFFFFFFFFFFFFFFF; + + /// @notice Reads a 64-bit word from memory. + /// @param _memRoot The current memory root + /// @param _addr The address to read from. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @return out_ The hashed MIPS state. + function readMem(bytes32 _memRoot, uint64 _addr, uint256 _proofOffset) internal pure returns (uint64 out_) { + bool valid; + (out_, valid) = readMemUnchecked(_memRoot, _addr, _proofOffset); + if (!valid) { + revert InvalidMemoryProof(); + } + } + + /// @notice Reads a 64-bit word from memory. + /// @param _memRoot The current memory root + /// @param _addr The address to read from. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @return out_ The hashed MIPS state. + /// valid_ Whether the proof is valid. + function readMemUnchecked( + bytes32 _memRoot, + uint64 _addr, + uint256 _proofOffset + ) + internal + pure + returns (uint64 out_, bool valid_) + { + unchecked { + validateMemoryProofAvailability(_proofOffset); + assembly { + // Validate the address alignment. + if and(_addr, EXT_MASK) { + // revert InvalidAddress(); + let ptr := mload(0x40) + mstore(ptr, shl(224, 0xe6c4247b)) + revert(ptr, 0x4) + } + + // Load the leaf value. + let leaf := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + + // Convenience function to hash two nodes together in scratch space. + function hashPair(a, b) -> h { + mstore(0, a) + mstore(32, b) + h := keccak256(0, 64) + } + + // Start with the leaf node. + // Work back up by combining with siblings, to reconstruct the root. + let path := shr(5, _addr) + let node := leaf + let end := sub(MEM_PROOF_LEAF_COUNT, 1) + for { let i := 0 } lt(i, end) { i := add(i, 1) } { + let sibling := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + switch and(shr(i, path), 1) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + + // Verify the root matches. + valid_ := eq(node, _memRoot) + if valid_ { + // Bits to shift = (32 - 8 - (addr % 32)) * 8 + let shamt := shl(3, sub(sub(32, 8), and(_addr, 31))) + out_ := and(shr(shamt, leaf), U64_MASK) + } + } + } + } + + /// @notice Writes a 64-bit word to memory. + /// This function first overwrites the part of the leaf. + /// Then it recomputes the memory merkle root. + /// @param _addr The address to write to. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @param _val The value to write. + /// @return newMemRoot_ The new memory root after modification + function writeMem(uint64 _addr, uint256 _proofOffset, uint64 _val) internal pure returns (bytes32 newMemRoot_) { + unchecked { + validateMemoryProofAvailability(_proofOffset); + assembly { + // Validate the address alignment. + if and(_addr, EXT_MASK) { + // revert InvalidAddress(); + let ptr := mload(0x40) + mstore(ptr, shl(224, 0xe6c4247b)) + revert(ptr, 0x4) + } + + // Load the leaf value. + let leaf := calldataload(_proofOffset) + let shamt := shl(3, sub(sub(32, 8), and(_addr, 31))) + + // Mask out 8 bytes, and OR in the value + leaf := or(and(leaf, not(shl(shamt, U64_MASK))), shl(shamt, _val)) + _proofOffset := add(_proofOffset, 32) + + // Convenience function to hash two nodes together in scratch space. + function hashPair(a, b) -> h { + mstore(0, a) + mstore(32, b) + h := keccak256(0, 64) + } + + // Start with the leaf node. + // Work back up by combining with siblings, to reconstruct the root. + let path := shr(5, _addr) + let node := leaf + let end := sub(MEM_PROOF_LEAF_COUNT, 1) + for { let i := 0 } lt(i, end) { i := add(i, 1) } { + let sibling := calldataload(_proofOffset) + _proofOffset := add(_proofOffset, 32) + switch and(shr(i, path), 1) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + + newMemRoot_ := node + } + return newMemRoot_; + } + } + + /// @notice Verifies a memory proof. + /// @param _memRoot The expected memory root + /// @param _addr The _addr proven. + /// @param _proofOffset The offset of the memory proof in calldata. + /// @return valid_ True iff it is a valid proof. + function isValidProof(bytes32 _memRoot, uint64 _addr, uint256 _proofOffset) internal pure returns (bool valid_) { + (, valid_) = readMemUnchecked(_memRoot, _addr, _proofOffset); + } + + /// @notice Computes the offset of a memory proof in the calldata. + /// @param _proofDataOffset The offset of the set of all memory proof data within calldata (proof.offset) + /// Equal to the offset of the first memory proof (at _proofIndex 0). + /// @param _proofIndex The index of the proof in the calldata. + /// @return offset_ The offset of the memory proof at the given _proofIndex in the calldata. + function memoryProofOffset(uint256 _proofDataOffset, uint8 _proofIndex) internal pure returns (uint256 offset_) { + unchecked { + // A proof of 64-bit memory, with 32-byte leaf values, is (64-5)=59 bytes32 entries. + // And the leaf value itself needs to be encoded as well: (59 + 1) = 60 bytes32 entries. + offset_ = _proofDataOffset + (uint256(_proofIndex) * (MEM_PROOF_LEAF_COUNT * 32)); + return offset_; + } + } + + /// @notice Validates that enough calldata is available to hold a full memory proof at the given offset + /// @param _proofStartOffset The index of the first byte of the target memory proof in calldata + function validateMemoryProofAvailability(uint256 _proofStartOffset) internal pure { + unchecked { + uint256 s = 0; + assembly { + s := calldatasize() + } + // A memory proof consists of MEM_PROOF_LEAF_COUNT bytes32 values - verify we have enough calldata + require( + s >= (_proofStartOffset + MEM_PROOF_LEAF_COUNT * 32), + "MIPS64Memory: check that there is enough calldata" + ); + } + } +} diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol new file mode 100644 index 0000000000000..c7102dea0fdd7 --- /dev/null +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Libraries +import { InvalidExitedValue } from "src/cannon/libraries/CannonErrors.sol"; + +library MIPS64State { + struct CpuScalars { + uint64 pc; + uint64 nextPC; + uint64 lo; + uint64 hi; + } + + function assertExitedIsValid(uint32 _exited) internal pure { + if (_exited > 1) { + revert InvalidExitedValue(); + } + } +} diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol new file mode 100644 index 0000000000000..a5b8201ca6557 --- /dev/null +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Syscalls.sol @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Libraries +import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; +import { MIPS64State as st } from "src/cannon/libraries/MIPS64State.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; +import { MIPS64Arch as arch } from "src/cannon/libraries/MIPS64Arch.sol"; + +library MIPS64Syscalls { + struct SysReadParams { + /// @param _a0 The file descriptor. + uint64 a0; + /// @param _a1 The memory location where data should be read to. + uint64 a1; + /// @param _a2 The number of bytes to read from the file + uint64 a2; + /// @param _preimageKey The key of the preimage to read. + bytes32 preimageKey; + /// @param _preimageOffset The offset of the preimage to read. + uint64 preimageOffset; + /// @param _localContext The local context for the preimage key. + bytes32 localContext; + /// @param _oracle The address of the preimage oracle. + IPreimageOracle oracle; + /// @param _proofOffset The offset of the memory proof in calldata. + uint256 proofOffset; + /// @param _memRoot The current memory root. + bytes32 memRoot; + } + + /// @custom:field _a0 The file descriptor. + /// @custom:field _a1 The memory address to read from. + /// @custom:field _a2 The number of bytes to read. + /// @custom:field _preimageKey The current preimaageKey. + /// @custom:field _preimageOffset The current preimageOffset. + /// @custom:field _proofOffset The offset of the memory proof in calldata. + /// @custom:field _memRoot The current memory root. + struct SysWriteParams { + uint64 _a0; + uint64 _a1; + uint64 _a2; + bytes32 _preimageKey; + uint64 _preimageOffset; + uint256 _proofOffset; + bytes32 _memRoot; + } + + uint64 internal constant U64_MASK = 0xFFffFFffFFffFFff; + uint64 internal constant PAGE_ADDR_MASK = 4095; + uint64 internal constant PAGE_SIZE = 4096; + + uint32 internal constant SYS_MMAP = 5009; + uint32 internal constant SYS_BRK = 5012; + uint32 internal constant SYS_CLONE = 5055; + uint32 internal constant SYS_EXIT_GROUP = 5205; + uint32 internal constant SYS_READ = 5000; + uint32 internal constant SYS_WRITE = 5001; + uint32 internal constant SYS_FCNTL = 5070; + uint32 internal constant SYS_EXIT = 5058; + uint32 internal constant SYS_SCHED_YIELD = 5023; + uint32 internal constant SYS_GETTID = 5178; + uint32 internal constant SYS_FUTEX = 5194; + uint32 internal constant SYS_OPEN = 5002; + uint32 internal constant SYS_NANOSLEEP = 5034; + uint32 internal constant SYS_CLOCKGETTIME = 5222; + uint32 internal constant SYS_GETPID = 5038; + // no-op syscalls + uint32 internal constant SYS_MUNMAP = 5011; + uint32 internal constant SYS_GETAFFINITY = 5196; + uint32 internal constant SYS_MADVISE = 5027; + uint32 internal constant SYS_RTSIGPROCMASK = 5014; + uint32 internal constant SYS_SIGALTSTACK = 5129; + uint32 internal constant SYS_RTSIGACTION = 5013; + uint32 internal constant SYS_PRLIMIT64 = 5297; + uint32 internal constant SYS_CLOSE = 5003; + uint32 internal constant SYS_PREAD64 = 5016; + uint32 internal constant SYS_STAT = 5004; + uint32 internal constant SYS_FSTAT = 5005; + //uint32 internal constant SYS_FSTAT64 = 0xFFFFFFFF; // UndefinedSysNr - not supported by MIPS64 + uint32 internal constant SYS_OPENAT = 5247; + uint32 internal constant SYS_READLINK = 5087; + uint32 internal constant SYS_READLINKAT = 5257; + uint32 internal constant SYS_IOCTL = 5015; + uint32 internal constant SYS_EPOLLCREATE1 = 5285; + uint32 internal constant SYS_PIPE2 = 5287; + uint32 internal constant SYS_EPOLLCTL = 5208; + uint32 internal constant SYS_EPOLLPWAIT = 5272; + uint32 internal constant SYS_GETRANDOM = 5313; + uint32 internal constant SYS_UNAME = 5061; + //uint32 internal constant SYS_STAT64 = 0xFFFFFFFF; // UndefinedSysNr - not supported by MIPS64 + uint32 internal constant SYS_GETUID = 5100; + uint32 internal constant SYS_GETGID = 5102; + //uint32 internal constant SYS_LLSEEK = 0xFFFFFFFF; // UndefinedSysNr - not supported by MIPS64 + uint32 internal constant SYS_MINCORE = 5026; + uint32 internal constant SYS_TGKILL = 5225; + uint32 internal constant SYS_GETRLIMIT = 5095; + uint32 internal constant SYS_LSEEK = 5008; + // profiling-related syscalls - ignored + uint32 internal constant SYS_SETITIMER = 5036; + uint32 internal constant SYS_TIMERCREATE = 5216; + uint32 internal constant SYS_TIMERSETTIME = 5217; + uint32 internal constant SYS_TIMERDELETE = 5220; + + uint32 internal constant FD_STDIN = 0; + uint32 internal constant FD_STDOUT = 1; + uint32 internal constant FD_STDERR = 2; + uint32 internal constant FD_HINT_READ = 3; + uint32 internal constant FD_HINT_WRITE = 4; + uint32 internal constant FD_PREIMAGE_READ = 5; + uint32 internal constant FD_PREIMAGE_WRITE = 6; + + uint64 internal constant SYS_ERROR_SIGNAL = U64_MASK; + uint64 internal constant EBADF = 0x9; + uint64 internal constant EINVAL = 0x16; + uint64 internal constant EAGAIN = 0xb; + uint64 internal constant ETIMEDOUT = 0x91; + + uint64 internal constant FUTEX_WAIT_PRIVATE = 128; + uint64 internal constant FUTEX_WAKE_PRIVATE = 129; + uint64 internal constant FUTEX_TIMEOUT_STEPS = 10000; + uint64 internal constant FUTEX_NO_TIMEOUT = type(uint64).max; + uint64 internal constant FUTEX_EMPTY_ADDR = U64_MASK; + + uint64 internal constant SCHED_QUANTUM = 100_000; + uint64 internal constant HZ = 10_000_000; + uint64 internal constant CLOCK_GETTIME_REALTIME_FLAG = 0; + uint64 internal constant CLOCK_GETTIME_MONOTONIC_FLAG = 1; + /// @notice Start of the data segment. + uint64 internal constant PROGRAM_BREAK = 0x00_00_40_00_00_00_00_00; + uint64 internal constant HEAP_END = 0x00_00_60_00_00_00_00_00; + + // SYS_CLONE flags + uint64 internal constant CLONE_VM = 0x100; + uint64 internal constant CLONE_FS = 0x200; + uint64 internal constant CLONE_FILES = 0x400; + uint64 internal constant CLONE_SIGHAND = 0x800; + uint64 internal constant CLONE_PTRACE = 0x2000; + uint64 internal constant CLONE_VFORK = 0x4000; + uint64 internal constant CLONE_PARENT = 0x8000; + uint64 internal constant CLONE_THREAD = 0x10000; + uint64 internal constant CLONE_NEWNS = 0x20000; + uint64 internal constant CLONE_SYSVSEM = 0x40000; + uint64 internal constant CLONE_SETTLS = 0x80000; + uint64 internal constant CLONE_PARENTSETTID = 0x100000; + uint64 internal constant CLONE_CHILDCLEARTID = 0x200000; + uint64 internal constant CLONE_UNTRACED = 0x800000; + uint64 internal constant CLONE_CHILDSETTID = 0x1000000; + uint64 internal constant CLONE_STOPPED = 0x2000000; + uint64 internal constant CLONE_NEWUTS = 0x4000000; + uint64 internal constant CLONE_NEWIPC = 0x8000000; + uint64 internal constant VALID_SYS_CLONE_FLAGS = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD; + + // FYI: https://en.wikibooks.org/wiki/MIPS_Assembly/Register_File + // https://refspecs.linuxfoundation.org/elf/mipsabi.pdf + uint32 internal constant REG_V0 = 2; + uint32 internal constant REG_A0 = 4; + uint32 internal constant REG_A1 = 5; + uint32 internal constant REG_A2 = 6; + uint32 internal constant REG_A3 = 7; + + // FYI: https://web.archive.org/web/20231223163047/https://www.linux-mips.org/wiki/Syscall + uint32 internal constant REG_SYSCALL_NUM = REG_V0; + uint32 internal constant REG_SYSCALL_ERRNO = REG_A3; + uint32 internal constant REG_SYSCALL_RET1 = REG_V0; + uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0; + uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1; + uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2; + uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3; + + // Constants copied from MIPS64Arch for use in Yul + uint64 internal constant WORD_SIZE_BYTES = 8; + uint64 internal constant EXT_MASK = 0x7; + + /// @notice Extract syscall num and arguments from registers. + /// @param _registers The cpu registers. + /// @return sysCallNum_ The syscall number. + /// @return a0_ The first argument available to the syscall operation. + /// @return a1_ The second argument available to the syscall operation. + /// @return a2_ The third argument available to the syscall operation. + /// @return a3_ The fourth argument available to the syscall operation. + function getSyscallArgs(uint64[32] memory _registers) + internal + pure + returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_, uint64 a3_) + { + unchecked { + sysCallNum_ = _registers[REG_SYSCALL_NUM]; + + a0_ = _registers[REG_SYSCALL_PARAM1]; + a1_ = _registers[REG_SYSCALL_PARAM2]; + a2_ = _registers[REG_SYSCALL_PARAM3]; + a3_ = _registers[REG_SYSCALL_PARAM4]; + + return (sysCallNum_, a0_, a1_, a2_, a3_); + } + } + + /// @notice Like a Linux mmap syscall. Allocates a page from the heap. + /// @param _a0 The address for the new mapping + /// @param _a1 The size of the new mapping + /// @param _heap The current value of the heap pointer + /// @return v0_ The address of the new mapping + /// @return v1_ Unused error code (0) + /// @return newHeap_ The new value for the heap, may be unchanged + function handleSysMmap( + uint64 _a0, + uint64 _a1, + uint64 _heap + ) + internal + pure + returns (uint64 v0_, uint64 v1_, uint64 newHeap_) + { + unchecked { + v1_ = uint64(0); + newHeap_ = _heap; + + uint64 sz = _a1; + if (sz & PAGE_ADDR_MASK != 0) { + // adjust size to align with page size + sz += PAGE_SIZE - (sz & PAGE_ADDR_MASK); + } + if (_a0 == 0) { + v0_ = _heap; + newHeap_ += sz; + // Fail if new heap exceeds memory limit, newHeap overflows to low memory, or sz overflows + if (newHeap_ > HEAP_END || newHeap_ < _heap || sz < _a1) { + v0_ = SYS_ERROR_SIGNAL; + v1_ = EINVAL; + return (v0_, v1_, _heap); + } + } else { + v0_ = _a0; + } + + return (v0_, v1_, newHeap_); + } + } + + /// @notice Like a Linux read syscall. Splits unaligned reads into aligned reads. + /// Args are provided as a struct to reduce stack pressure. + /// @return v0_ The number of bytes read, -1 on error. + /// @return v1_ The error code, 0 if there is no error. + /// @return newPreimageOffset_ The new value for the preimage offset. + /// @return newMemRoot_ The new memory root. + function handleSysRead(SysReadParams memory _args) + internal + view + returns ( + uint64 v0_, + uint64 v1_, + uint64 newPreimageOffset_, + bytes32 newMemRoot_, + bool memUpdated_, + uint64 memAddr_ + ) + { + unchecked { + v0_ = uint64(0); + v1_ = uint64(0); + newMemRoot_ = _args.memRoot; + newPreimageOffset_ = _args.preimageOffset; + memUpdated_ = false; + memAddr_ = 0; + + // args: _a0 = fd, _a1 = addr, _a2 = count + // returns: v0_ = read, v1_ = err code + if (_args.a0 == FD_STDIN) { + // Leave v0_ and v1_ zero: read nothing, no error + } + // pre-image oracle read + else if (_args.a0 == FD_PREIMAGE_READ) { + uint64 effAddr = _args.a1 & arch.ADDRESS_MASK; + // verify proof is correct, and get the existing memory. + // mask the addr to align it to 4 bytes + uint64 mem = MIPS64Memory.readMem(_args.memRoot, effAddr, _args.proofOffset); + // If the preimage key is a local key, localize it in the context of the caller. + if (uint8(_args.preimageKey[0]) == 1) { + _args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext); + } + (bytes32 dat, uint256 datLen) = _args.oracle.readPreimage(_args.preimageKey, _args.preimageOffset); + + // Transform data for writing to memory + // We use assembly for more precise ops, and no var count limit + uint64 a1 = _args.a1; + uint64 a2 = _args.a2; + assembly { + let alignment := and(a1, EXT_MASK) // the read might not start at an aligned address + let space := sub(WORD_SIZE_BYTES, alignment) // remaining space in memory word + if lt(space, datLen) { datLen := space } // if less space than data, shorten data + if lt(a2, datLen) { datLen := a2 } // if requested to read less, read less + dat := shr(sub(256, mul(datLen, 8)), dat) // right-align data + // position data to insert into memory word + dat := shl(mul(sub(sub(WORD_SIZE_BYTES, datLen), alignment), 8), dat) + // mask all bytes after start + let mask := sub(shl(mul(sub(WORD_SIZE_BYTES, alignment), 8), 1), 1) + // mask of all bytes + let suffixMask := sub(shl(mul(sub(sub(WORD_SIZE_BYTES, alignment), datLen), 8), 1), 1) + // starting from end, maybe none + mask := and(mask, not(suffixMask)) // reduce mask to just cover the data we insert + mem := or(and(mem, not(mask)), dat) // clear masked part of original memory, and insert data + } + + // Write memory back + newMemRoot_ = MIPS64Memory.writeMem(effAddr, _args.proofOffset, mem); + memUpdated_ = true; + memAddr_ = effAddr; + newPreimageOffset_ += uint64(datLen); + v0_ = uint64(datLen); + } + // hint response + else if (_args.a0 == FD_HINT_READ) { + // Don't read into memory, just say we read it all + // The result is ignored anyway + v0_ = _args.a2; + } else { + v0_ = U64_MASK; + v1_ = EBADF; + } + + return (v0_, v1_, newPreimageOffset_, newMemRoot_, memUpdated_, memAddr_); + } + } + + /// @notice Like a Linux write syscall. Splits unaligned writes into aligned writes. + /// @return v0_ The number of bytes written, or -1 on error. + /// @return v1_ The error code, or 0 if empty. + /// @return newPreimageKey_ The new preimageKey. + /// @return newPreimageOffset_ The new preimageOffset. + function handleSysWrite(SysWriteParams memory _args) + internal + pure + returns (uint64 v0_, uint64 v1_, bytes32 newPreimageKey_, uint64 newPreimageOffset_) + { + unchecked { + // args: _a0 = fd, _a1 = addr, _a2 = count + // returns: v0_ = written, v1_ = err code + v0_ = uint64(0); + v1_ = uint64(0); + newPreimageKey_ = _args._preimageKey; + newPreimageOffset_ = _args._preimageOffset; + + if (_args._a0 == FD_STDOUT || _args._a0 == FD_STDERR || _args._a0 == FD_HINT_WRITE) { + v0_ = _args._a2; // tell program we have written everything + } + // pre-image oracle + else if (_args._a0 == FD_PREIMAGE_WRITE) { + // mask the addr to align it to 4 bytes + uint64 mem = MIPS64Memory.readMem(_args._memRoot, _args._a1 & arch.ADDRESS_MASK, _args._proofOffset); + bytes32 key = _args._preimageKey; + + // Construct pre-image key from memory + // We use assembly for more precise ops, and no var count limit + uint64 _a1 = _args._a1; + uint64 _a2 = _args._a2; + assembly { + let alignment := and(_a1, EXT_MASK) // the read might not start at an aligned address + let space := sub(WORD_SIZE_BYTES, alignment) // remaining space in memory word + if lt(space, _a2) { _a2 := space } // if less space than data, shorten data + key := shl(mul(_a2, 8), key) // shift key, make space for new info + let mask := sub(shl(mul(_a2, 8), 1), 1) // mask for extracting value from memory + mem := and(shr(mul(sub(space, _a2), 8), mem), mask) // align value to right, mask it + key := or(key, mem) // insert into key + } + _args._a2 = _a2; + + // Write pre-image key to oracle + newPreimageKey_ = key; + newPreimageOffset_ = 0; // reset offset, to read new pre-image data from the start + v0_ = _args._a2; + } else { + v0_ = U64_MASK; + v1_ = EBADF; + } + + return (v0_, v1_, newPreimageKey_, newPreimageOffset_); + } + } + + /// @notice Like Linux fcntl (file control) syscall, but only supports minimal file-descriptor control commands, to + /// retrieve the file-descriptor R/W flags. + /// @param _a0 The file descriptor. + /// @param _a1 The control command. + /// @param v0_ The file status flag (only supported commands are F_GETFD and F_GETFL), or -1 on error. + /// @param v1_ An error number, or 0 if there is no error. + function handleSysFcntl(uint64 _a0, uint64 _a1) internal pure returns (uint64 v0_, uint64 v1_) { + unchecked { + v0_ = uint64(0); + v1_ = uint64(0); + + // args: _a0 = fd, _a1 = cmd + if (_a1 == 1) { + // F_GETFD: get file descriptor flags + if ( + _a0 == FD_STDIN || _a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_READ + || _a0 == FD_HINT_READ || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE + ) { + v0_ = 0; // No flags set + } else { + v0_ = U64_MASK; + v1_ = EBADF; + } + } else if (_a1 == 3) { + // F_GETFL: get file status flags + if (_a0 == FD_STDIN || _a0 == FD_PREIMAGE_READ || _a0 == FD_HINT_READ) { + v0_ = 0; // O_RDONLY + } else if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE) { + v0_ = 1; // O_WRONLY + } else { + v0_ = U64_MASK; + v1_ = EBADF; + } + } else { + v0_ = U64_MASK; + v1_ = EINVAL; // cmd not recognized by this kernel + } + + return (v0_, v1_); + } + } + + function handleSyscallUpdates( + st.CpuScalars memory _cpu, + uint64[32] memory _registers, + uint64 _v0, + uint64 _v1 + ) + internal + pure + { + unchecked { + // Write the results back to the state registers + _registers[REG_SYSCALL_RET1] = _v0; + _registers[REG_SYSCALL_ERRNO] = _v1; + + // Update the PC and nextPC + _cpu.pc = _cpu.nextPC; + _cpu.nextPC = _cpu.nextPC + 4; + } + } +} diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol index a2402531c82d1..70ed5e20c3bc9 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPSInstructions.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; +// Libraries import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; library MIPSInstructions { uint32 internal constant OP_LOAD_LINKED = 0x30; uint32 internal constant OP_STORE_CONDITIONAL = 0x38; + uint32 internal constant REG_RA = 31; struct CoreStepLogicParams { /// @param opcode The opcode value parsed from insn_. @@ -378,11 +380,11 @@ library MIPSInstructions { } // lb else if (_opcode == 0x20) { - return signExtend((_mem >> (24 - (_rs & 3) * 8)) & 0xFF, 8); + return selectSubWord(_rs, _mem, 1, true); } // lh else if (_opcode == 0x21) { - return signExtend((_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF, 16); + return selectSubWord(_rs, _mem, 2, true); } // lwl else if (_opcode == 0x22) { @@ -392,15 +394,15 @@ library MIPSInstructions { } // lw else if (_opcode == 0x23) { - return _mem; + return selectSubWord(_rs, _mem, 4, true); } // lbu else if (_opcode == 0x24) { - return (_mem >> (24 - (_rs & 3) * 8)) & 0xFF; + return selectSubWord(_rs, _mem, 1, false); } // lhu else if (_opcode == 0x25) { - return (_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF; + return selectSubWord(_rs, _mem, 2, false); } // lwr else if (_opcode == 0x26) { @@ -410,15 +412,11 @@ library MIPSInstructions { } // sb else if (_opcode == 0x28) { - uint32 val = (_rt & 0xFF) << (24 - (_rs & 3) * 8); - uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (_rs & 3) * 8)); - return (_mem & mask) | val; + return updateSubWord(_rs, _mem, 1, _rt); } // sh else if (_opcode == 0x29) { - uint32 val = (_rt & 0xFFFF) << (16 - (_rs & 2) * 8); - uint32 mask = 0xFFFFFFFF ^ uint32(0xFFFF << (16 - (_rs & 2) * 8)); - return (_mem & mask) | val; + return updateSubWord(_rs, _mem, 2, _rt); } // swl else if (_opcode == 0x2a) { @@ -428,7 +426,7 @@ library MIPSInstructions { } // sw else if (_opcode == 0x2b) { - return _rt; + return updateSubWord(_rs, _mem, 4, _rt); } // swr else if (_opcode == 0x2e) { @@ -498,9 +496,19 @@ library MIPSInstructions { if (rtv == 0) { shouldBranch = int32(_rs) < 0; } + // bltzal + if (rtv == 0x10) { + shouldBranch = int32(_rs) < 0; + _registers[31] = _cpu.pc + 8; // always set regardless of branch taken + } if (rtv == 1) { shouldBranch = int32(_rs) >= 0; } + // bgezal (i.e. bal mnemonic) + if (rtv == 0x11) { + shouldBranch = int32(_rs) >= 0; + _registers[REG_RA] = _cpu.pc + 8; // always set regardless of branch taken + } } // Update the state's previous PC @@ -661,4 +669,69 @@ library MIPSInstructions { _cpu.nextPC = _cpu.nextPC + 4; } } + + /// @notice Selects a subword of byteLength size contained in memWord based on the low-order bits of vaddr + /// @param _vaddr The virtual address of the the subword. + /// @param _memWord The full word to select a subword from. + /// @param _byteLength The size of the subword. + /// @param _signExtend Whether to sign extend the selected subwrod. + function selectSubWord( + uint32 _vaddr, + uint32 _memWord, + uint32 _byteLength, + bool _signExtend + ) + internal + pure + returns (uint32 retval_) + { + (uint32 dataMask, uint32 bitOffset, uint32 bitLength) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); + retval_ = (_memWord >> bitOffset) & dataMask; + if (_signExtend) { + retval_ = signExtend(retval_, bitLength); + } + return retval_; + } + + /// @notice Returns a word that has been updated by the specified subword at bit positions determined by the virtual + /// address + /// @param _vaddr The virtual address of the subword. + /// @param _memWord The full word to update. + /// @param _byteLength The size of the subword. + /// @param _value The subword that updates _memWord. + function updateSubWord( + uint32 _vaddr, + uint32 _memWord, + uint32 _byteLength, + uint32 _value + ) + internal + pure + returns (uint32 word_) + { + (uint32 dataMask, uint32 bitOffset,) = calculateSubWordMaskAndOffset(_vaddr, _byteLength); + uint32 subWordValue = dataMask & _value; + uint32 memUpdateMask = dataMask << bitOffset; + return subWordValue << bitOffset | (~memUpdateMask) & _memWord; + } + + function calculateSubWordMaskAndOffset( + uint32 _vaddr, + uint32 _byteLength + ) + internal + pure + returns (uint32 dataMask_, uint32 bitOffset_, uint32 bitLength_) + { + uint32 bitLength = _byteLength << 3; + uint32 dataMask = ~uint32(0) >> (32 - bitLength); + + // Figure out sub-word index based on the low-order bits in vaddr + uint32 byteIndexMask = _vaddr & 0x3 & ~(_byteLength - 1); + uint32 maxByteShift = 4 - _byteLength; + uint32 byteIndex = _vaddr & byteIndexMask; + uint32 bitOffset = (maxByteShift - byteIndex) << 3; + + return (dataMask, bitOffset, bitLength); + } } diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol index e88f51438ef48..1d3942f705096 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPSMemory.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; -import "src/cannon/libraries/CannonErrors.sol"; +// Libraries +import { InvalidMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; library MIPSMemory { /// @notice Reads a 32-bit value from memory. diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol index f9631e29e0824..b2982b5b16afd 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPSState.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; +// Libraries import { InvalidExitedValue } from "src/cannon/libraries/CannonErrors.sol"; library MIPSState { diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol index 968faaf9aea74..8fa62dbbad787 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.0; +// Libraries import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; library MIPSSyscalls { @@ -28,6 +29,23 @@ library MIPSSyscalls { bytes32 memRoot; } + /// @custom:field _a0 The file descriptor. + /// @custom:field _a1 The memory address to read from. + /// @custom:field _a2 The number of bytes to read. + /// @custom:field _preimageKey The current preimaageKey. + /// @custom:field _preimageOffset The current preimageOffset. + /// @custom:field _proofOffset The offset of the memory proof in calldata. + /// @custom:field _memRoot The current memory root. + struct SysWriteParams { + uint32 _a0; + uint32 _a1; + uint32 _a2; + bytes32 _preimageKey; + uint32 _preimageOffset; + uint256 _proofOffset; + bytes32 _memRoot; + } + uint32 internal constant SYS_MMAP = 4090; uint32 internal constant SYS_BRK = 4045; uint32 internal constant SYS_CLONE = 4120; @@ -53,6 +71,7 @@ library MIPSSyscalls { uint32 internal constant SYS_PRLIMIT64 = 4338; uint32 internal constant SYS_CLOSE = 4006; uint32 internal constant SYS_PREAD64 = 4200; + uint32 internal constant SYS_STAT = 4106; uint32 internal constant SYS_FSTAT = 4108; uint32 internal constant SYS_FSTAT64 = 4215; uint32 internal constant SYS_OPENAT = 4288; @@ -71,6 +90,8 @@ library MIPSSyscalls { uint32 internal constant SYS_LLSEEK = 4140; uint32 internal constant SYS_MINCORE = 4217; uint32 internal constant SYS_TGKILL = 4266; + uint32 internal constant SYS_GETRLIMIT = 4076; + uint32 internal constant SYS_LSEEK = 4019; // profiling-related syscalls - ignored uint32 internal constant SYS_SETITIMER = 4104; @@ -128,6 +149,23 @@ library MIPSSyscalls { uint32 internal constant VALID_SYS_CLONE_FLAGS = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD; + // FYI: https://en.wikibooks.org/wiki/MIPS_Assembly/Register_File + // https://refspecs.linuxfoundation.org/elf/mipsabi.pdf + uint32 internal constant REG_V0 = 2; + uint32 internal constant REG_A0 = 4; + uint32 internal constant REG_A1 = 5; + uint32 internal constant REG_A2 = 6; + uint32 internal constant REG_A3 = 7; + + // FYI: https://web.archive.org/web/20231223163047/https://www.linux-mips.org/wiki/Syscall + uint32 internal constant REG_SYSCALL_NUM = REG_V0; + uint32 internal constant REG_SYSCALL_ERRNO = REG_A3; + uint32 internal constant REG_SYSCALL_RET1 = REG_V0; + uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0; + uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1; + uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2; + uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3; + /// @notice Extract syscall num and arguments from registers. /// @param _registers The cpu registers. /// @return sysCallNum_ The syscall number. @@ -141,12 +179,12 @@ library MIPSSyscalls { returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_, uint32 a3_) { unchecked { - sysCallNum_ = _registers[2]; + sysCallNum_ = _registers[REG_SYSCALL_NUM]; - a0_ = _registers[4]; - a1_ = _registers[5]; - a2_ = _registers[6]; - a3_ = _registers[7]; + a0_ = _registers[REG_SYSCALL_PARAM1]; + a1_ = _registers[REG_SYSCALL_PARAM2]; + a2_ = _registers[REG_SYSCALL_PARAM3]; + a3_ = _registers[REG_SYSCALL_PARAM4]; return (sysCallNum_, a0_, a1_, a2_, a3_); } @@ -278,26 +316,11 @@ library MIPSSyscalls { } /// @notice Like a Linux write syscall. Splits unaligned writes into aligned writes. - /// @param _a0 The file descriptor. - /// @param _a1 The memory address to read from. - /// @param _a2 The number of bytes to read. - /// @param _preimageKey The current preimaageKey. - /// @param _preimageOffset The current preimageOffset. - /// @param _proofOffset The offset of the memory proof in calldata. - /// @param _memRoot The current memory root. /// @return v0_ The number of bytes written, or -1 on error. /// @return v1_ The error code, or 0 if empty. /// @return newPreimageKey_ The new preimageKey. /// @return newPreimageOffset_ The new preimageOffset. - function handleSysWrite( - uint32 _a0, - uint32 _a1, - uint32 _a2, - bytes32 _preimageKey, - uint32 _preimageOffset, - uint256 _proofOffset, - bytes32 _memRoot - ) + function handleSysWrite(SysWriteParams memory _args) internal pure returns (uint32 v0_, uint32 v1_, bytes32 newPreimageKey_, uint32 newPreimageOffset_) @@ -307,20 +330,22 @@ library MIPSSyscalls { // returns: v0_ = written, v1_ = err code v0_ = uint32(0); v1_ = uint32(0); - newPreimageKey_ = _preimageKey; - newPreimageOffset_ = _preimageOffset; + newPreimageKey_ = _args._preimageKey; + newPreimageOffset_ = _args._preimageOffset; - if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_HINT_WRITE) { - v0_ = _a2; // tell program we have written everything + if (_args._a0 == FD_STDOUT || _args._a0 == FD_STDERR || _args._a0 == FD_HINT_WRITE) { + v0_ = _args._a2; // tell program we have written everything } // pre-image oracle - else if (_a0 == FD_PREIMAGE_WRITE) { + else if (_args._a0 == FD_PREIMAGE_WRITE) { // mask the addr to align it to 4 bytes - uint32 mem = MIPSMemory.readMem(_memRoot, _a1 & 0xFFffFFfc, _proofOffset); - bytes32 key = _preimageKey; + uint32 mem = MIPSMemory.readMem(_args._memRoot, _args._a1 & 0xFFffFFfc, _args._proofOffset); + bytes32 key = _args._preimageKey; // Construct pre-image key from memory // We use assembly for more precise ops, and no var count limit + uint32 _a1 = _args._a1; + uint32 _a2 = _args._a2; assembly { let alignment := and(_a1, 3) // the read might not start at an aligned address let space := sub(4, alignment) // remaining space in memory word @@ -330,11 +355,12 @@ library MIPSSyscalls { mem := and(shr(mul(sub(space, _a2), 8), mem), mask) // align value to right, mask it key := or(key, mem) // insert into key } + _args._a2 = _a2; // Write pre-image key to oracle newPreimageKey_ = key; newPreimageOffset_ = 0; // reset offset, to read new pre-image data from the start - v0_ = _a2; + v0_ = _args._a2; } else { v0_ = 0xFFffFFff; v1_ = EBADF; @@ -397,8 +423,8 @@ library MIPSSyscalls { { unchecked { // Write the results back to the state registers - _registers[2] = _v0; - _registers[7] = _v1; + _registers[REG_SYSCALL_RET1] = _v0; + _registers[REG_SYSCALL_ERRNO] = _v1; // Update the PC and nextPC _cpu.pc = _cpu.nextPC; diff --git a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol index 5486d83f76847..0f48b9f2a4e9a 100644 --- a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol +++ b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol @@ -5,16 +5,16 @@ pragma solidity 0.8.15; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; // Libraries -import "src/dispute/lib/Types.sol"; +import { GameType, OutputRoot, Claim, GameStatus, Hash } from "src/dispute/lib/Types.sol"; import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; import { UnregisteredGame, InvalidGameStatus } from "src/dispute/lib/Errors.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title AnchorStateRegistry @@ -30,8 +30,8 @@ contract AnchorStateRegistry is Initializable, ISemver { } /// @notice Semantic version. - /// @custom:semver 2.0.1-beta.3 - string public constant version = "2.0.1-beta.3"; + /// @custom:semver 2.0.1-beta.5 + string public constant version = "2.0.1-beta.5"; /// @notice DisputeGameFactory address. IDisputeGameFactory internal immutable DISPUTE_GAME_FACTORY; diff --git a/packages/contracts-bedrock/src/dispute/DelayedWETH.sol b/packages/contracts-bedrock/src/dispute/DelayedWETH.sol index 42b6022d12fe4..f3966d8ac89f3 100644 --- a/packages/contracts-bedrock/src/dispute/DelayedWETH.sol +++ b/packages/contracts-bedrock/src/dispute/DelayedWETH.sol @@ -6,8 +6,8 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O import { WETH98 } from "src/universal/WETH98.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title DelayedWETH @@ -32,8 +32,8 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver { event Unwrap(address indexed src, uint256 wad); /// @notice Semantic version. - /// @custom:semver 1.2.0-beta.2 - string public constant version = "1.2.0-beta.2"; + /// @custom:semver 1.2.0-beta.4 + string public constant version = "1.2.0-beta.4"; /// @notice Returns a withdrawal request for the given address. mapping(address => mapping(address => WithdrawalRequest)) public withdrawals; @@ -112,7 +112,7 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver { /// @param _wad The amount of WETH to recover. function hold(address _guy, uint256 _wad) external { require(msg.sender == owner(), "DelayedWETH: not owner"); - allowance[_guy][msg.sender] = _wad; + _allowance[_guy][msg.sender] = _wad; emit Approval(_guy, msg.sender, _wad); } } diff --git a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol index dee8dd75ec600..583f432f3e0c7 100644 --- a/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol +++ b/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol @@ -6,12 +6,12 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O // Libraries import { LibClone } from "@solady/utils/LibClone.sol"; -import "src/dispute/lib/Types.sol"; -import "src/dispute/lib/Errors.sol"; +import { GameType, Claim, GameId, Timestamp, Hash, LibGameId } from "src/dispute/lib/Types.sol"; +import { NoImplementation, IncorrectBondAmount, GameAlreadyExists } from "src/dispute/lib/Errors.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; /// @custom:proxied true /// @title DisputeGameFactory @@ -49,8 +49,8 @@ contract DisputeGameFactory is OwnableUpgradeable, ISemver { } /// @notice Semantic version. - /// @custom:semver 1.0.1-beta.2 - string public constant version = "1.0.1-beta.2"; + /// @custom:semver 1.0.1-beta.4 + string public constant version = "1.0.1-beta.4"; /// @notice `gameImpls` is a mapping that maps `GameType`s to their respective /// `IDisputeGame` implementations. diff --git a/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol b/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol index ccd29fac9d117..02a2cee3ca834 100644 --- a/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol @@ -8,14 +8,57 @@ import { Clone } from "@solady/utils/Clone.sol"; import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; -import "src/dispute/lib/Types.sol"; -import "src/dispute/lib/Errors.sol"; +import { + GameStatus, + GameType, + Claim, + Clock, + Duration, + Timestamp, + Hash, + OutputRoot, + LibClock, + LocalPreimageKey, + VMStatuses +} from "src/dispute/lib/Types.sol"; +import { Position, LibPosition } from "src/dispute/lib/LibPosition.sol"; +import { + InvalidParent, + ClaimAlreadyExists, + ClaimAlreadyResolved, + OutOfOrderResolution, + InvalidChallengePeriod, + InvalidSplitDepth, + InvalidClockExtension, + MaxDepthTooLarge, + AnchorRootNotFound, + AlreadyInitialized, + UnexpectedRootClaim, + GameNotInProgress, + InvalidPrestate, + ValidStep, + GameDepthExceeded, + L2BlockNumberChallenged, + InvalidDisputedClaimIndex, + ClockTimeExceeded, + DuplicateStep, + CannotDefendRootClaim, + IncorrectBondAmount, + InvalidLocalIdent, + BlockNumberMatches, + InvalidHeaderRLP, + ClockNotExpired, + BondTransferFailed, + NoCreditToClaim, + InvalidOutputRootProof, + ClaimAboveSplit +} from "src/dispute/lib/Errors.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IBigStepper, IPreimageOracle } from "interfaces/dispute/IBigStepper.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; /// @title FaultDisputeGame /// @notice An implementation of the `IFaultDisputeGame` interface. @@ -43,6 +86,21 @@ contract FaultDisputeGame is Clone, ISemver { address counteredBy; } + /// @notice Parameters for creating a new FaultDisputeGame. We place this into a struct to + /// avoid stack-too-deep errors when compiling without the optimizer enabled. + struct GameConstructorParams { + GameType gameType; + Claim absolutePrestate; + uint256 maxGameDepth; + uint256 splitDepth; + Duration clockExtension; + Duration maxClockDuration; + IBigStepper vm; + IDelayedWETH weth; + IAnchorStateRegistry anchorStateRegistry; + uint256 l2ChainId; + } + //////////////////////////////////////////////////////////////// // Events // //////////////////////////////////////////////////////////////// @@ -103,8 +161,8 @@ contract FaultDisputeGame is Clone, ISemver { uint256 internal constant HEADER_BLOCK_NUMBER_INDEX = 8; /// @notice Semantic version. - /// @custom:semver 1.3.1-beta.4 - string public constant version = "1.3.1-beta.4"; + /// @custom:semver 1.3.1-beta.9 + string public constant version = "1.3.1-beta.9"; /// @notice The starting timestamp of the game Timestamp public createdAt; @@ -146,69 +204,52 @@ contract FaultDisputeGame is Clone, ISemver { /// @notice The latest finalized output root, serving as the anchor for output bisection. OutputRoot public startingOutputRoot; - /// @param _gameType The type ID of the game. - /// @param _absolutePrestate The absolute prestate of the instruction trace. - /// @param _maxGameDepth The maximum depth of bisection. - /// @param _splitDepth The final depth of the output bisection portion of the game. - /// @param _clockExtension The clock extension to perform when the remaining duration is less than the extension. - /// @param _maxClockDuration The maximum amount of time that may accumulate on a team's chess clock. - /// @param _vm An onchain VM that performs single instruction steps on an FPP trace. - /// @param _weth WETH contract for holding ETH. - /// @param _anchorStateRegistry The contract that stores the anchor state for each game type. - /// @param _l2ChainId Chain ID of the L2 network this contract argues about. - constructor( - GameType _gameType, - Claim _absolutePrestate, - uint256 _maxGameDepth, - uint256 _splitDepth, - Duration _clockExtension, - Duration _maxClockDuration, - IBigStepper _vm, - IDelayedWETH _weth, - IAnchorStateRegistry _anchorStateRegistry, - uint256 _l2ChainId - ) { + /// @param _params Parameters for creating a new FaultDisputeGame. + constructor(GameConstructorParams memory _params) { // The max game depth may not be greater than `LibPosition.MAX_POSITION_BITLEN - 1`. - if (_maxGameDepth > LibPosition.MAX_POSITION_BITLEN - 1) revert MaxDepthTooLarge(); + if (_params.maxGameDepth > LibPosition.MAX_POSITION_BITLEN - 1) revert MaxDepthTooLarge(); // The split depth plus one cannot be greater than or equal to the max game depth. We add // an additional depth to the split depth to avoid a bug in trace ancestor lookup. We know // that the case where the split depth is the max value for uint256 is equivalent to the // second check though we do need to check it explicitly to avoid an overflow. - if (_splitDepth == type(uint256).max || _splitDepth + 1 >= _maxGameDepth) revert InvalidSplitDepth(); + if (_params.splitDepth == type(uint256).max || _params.splitDepth + 1 >= _params.maxGameDepth) { + revert InvalidSplitDepth(); + } // The split depth cannot be 0 or 1 to stay in bounds of clock extension arithmetic. - if (_splitDepth < 2) revert InvalidSplitDepth(); + if (_params.splitDepth < 2) revert InvalidSplitDepth(); // The PreimageOracle challenge period must fit into uint64 so we can safely use it here. // Runtime check was added instead of changing the ABI since the contract is already // deployed in production. We perform the same check within the PreimageOracle for the // benefit of developers but also perform this check here defensively. - if (_vm.oracle().challengePeriod() > type(uint64).max) revert InvalidChallengePeriod(); + if (_params.vm.oracle().challengePeriod() > type(uint64).max) revert InvalidChallengePeriod(); // Determine the maximum clock extension which is either the split depth extension or the // maximum game depth extension depending on the configuration of these contracts. - uint256 splitDepthExtension = uint256(_clockExtension.raw()) * 2; - uint256 maxGameDepthExtension = uint256(_clockExtension.raw()) + uint256(_vm.oracle().challengePeriod()); + uint256 splitDepthExtension = uint256(_params.clockExtension.raw()) * 2; + uint256 maxGameDepthExtension = + uint256(_params.clockExtension.raw()) + uint256(_params.vm.oracle().challengePeriod()); uint256 maxClockExtension = Math.max(splitDepthExtension, maxGameDepthExtension); // The maximum clock extension must fit into a uint64. if (maxClockExtension > type(uint64).max) revert InvalidClockExtension(); // The maximum clock extension may not be greater than the maximum clock duration. - if (uint64(maxClockExtension) > _maxClockDuration.raw()) revert InvalidClockExtension(); + if (uint64(maxClockExtension) > _params.maxClockDuration.raw()) revert InvalidClockExtension(); // Set up initial game state. - GAME_TYPE = _gameType; - ABSOLUTE_PRESTATE = _absolutePrestate; - MAX_GAME_DEPTH = _maxGameDepth; - SPLIT_DEPTH = _splitDepth; - CLOCK_EXTENSION = _clockExtension; - MAX_CLOCK_DURATION = _maxClockDuration; - VM = _vm; - WETH = _weth; - ANCHOR_STATE_REGISTRY = _anchorStateRegistry; - L2_CHAIN_ID = _l2ChainId; + GAME_TYPE = _params.gameType; + ABSOLUTE_PRESTATE = _params.absolutePrestate; + MAX_GAME_DEPTH = _params.maxGameDepth; + SPLIT_DEPTH = _params.splitDepth; + CLOCK_EXTENSION = _params.clockExtension; + MAX_CLOCK_DURATION = _params.maxClockDuration; + VM = _params.vm; + WETH = _params.weth; + ANCHOR_STATE_REGISTRY = _params.anchorStateRegistry; + L2_CHAIN_ID = _params.l2ChainId; } /// @notice Initializes the contract. diff --git a/packages/contracts-bedrock/src/dispute/PermissionedDisputeGame.sol b/packages/contracts-bedrock/src/dispute/PermissionedDisputeGame.sol index abd8a8896cc32..de907695d0063 100644 --- a/packages/contracts-bedrock/src/dispute/PermissionedDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/PermissionedDisputeGame.sol @@ -5,13 +5,8 @@ pragma solidity 0.8.15; import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol"; // Libraries -import "src/dispute/lib/Types.sol"; -import "src/dispute/lib/Errors.sol"; - -// Interfaces -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; +import { Claim } from "src/dispute/lib/Types.sol"; +import { BadAuth } from "src/dispute/lib/Errors.sol"; /// @title PermissionedDisputeGame /// @notice PermissionedDisputeGame is a contract that inherits from `FaultDisputeGame`, and contains two roles: @@ -36,44 +31,15 @@ contract PermissionedDisputeGame is FaultDisputeGame { _; } - /// @param _gameType The type ID of the game. - /// @param _absolutePrestate The absolute prestate of the instruction trace. - /// @param _maxGameDepth The maximum depth of bisection. - /// @param _splitDepth The final depth of the output bisection portion of the game. - /// @param _clockExtension The clock extension to perform when the remaining duration is less than the extension. - /// @param _maxClockDuration The maximum amount of time that may accumulate on a team's chess clock. - /// @param _vm An onchain VM that performs single instruction steps on an FPP trace. - /// @param _weth WETH contract for holding ETH. - /// @param _anchorStateRegistry The contract that stores the anchor state for each game type. - /// @param _l2ChainId Chain ID of the L2 network this contract argues about. + /// @param _params Parameters for creating a new FaultDisputeGame. /// @param _proposer Address that is allowed to create instances of this contract. /// @param _challenger Address that is allowed to challenge instances of this contract. constructor( - GameType _gameType, - Claim _absolutePrestate, - uint256 _maxGameDepth, - uint256 _splitDepth, - Duration _clockExtension, - Duration _maxClockDuration, - IBigStepper _vm, - IDelayedWETH _weth, - IAnchorStateRegistry _anchorStateRegistry, - uint256 _l2ChainId, + GameConstructorParams memory _params, address _proposer, address _challenger ) - FaultDisputeGame( - _gameType, - _absolutePrestate, - _maxGameDepth, - _splitDepth, - _clockExtension, - _maxClockDuration, - _vm, - _weth, - _anchorStateRegistry, - _l2ChainId - ) + FaultDisputeGame(_params) { PROPOSER = _proposer; CHALLENGER = _challenger; diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IDelayedWETH.sol b/packages/contracts-bedrock/src/dispute/interfaces/IDelayedWETH.sol deleted file mode 100644 index 55b940c2d9dd2..0000000000000 --- a/packages/contracts-bedrock/src/dispute/interfaces/IDelayedWETH.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IWETH } from "src/universal/interfaces/IWETH.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; - -interface IDelayedWETH is IWETH { - struct WithdrawalRequest { - uint256 amount; - uint256 timestamp; - } - - event Unwrap(address indexed src, uint256 wad); - - fallback() external payable; - receive() external payable; - - function config() external view returns (ISuperchainConfig); - function delay() external view returns (uint256); - function hold(address _guy, uint256 _wad) external; - function initialize(address _owner, ISuperchainConfig _config) external; - function owner() external view returns (address); - function recover(uint256 _wad) external; - function transferOwnership(address newOwner) external; // nosemgrep - function renounceOwnership() external; - function unlock(address _guy, uint256 _wad) external; - function withdraw(address _guy, uint256 _wad) external; - function withdrawals(address _owner, address _guy) external view returns (uint256, uint256); - function version() external view returns (string memory); - - function __constructor__(uint256 _delay) external; -} diff --git a/packages/contracts-bedrock/src/dispute/lib/Errors.sol b/packages/contracts-bedrock/src/dispute/lib/Errors.sol index 6cd1d1d073f46..0dd4af1875059 100644 --- a/packages/contracts-bedrock/src/dispute/lib/Errors.sol +++ b/packages/contracts-bedrock/src/dispute/lib/Errors.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -import "src/dispute/lib/LibUDT.sol"; +// Libraries +import { GameType, Hash, Claim } from "src/dispute/lib/LibUDT.sol"; //////////////////////////////////////////////////////////////// // `DisputeGameFactory` Errors // diff --git a/packages/contracts-bedrock/src/dispute/lib/LibUDT.sol b/packages/contracts-bedrock/src/dispute/lib/LibUDT.sol index b0b8edfab8f49..780738a79743f 100644 --- a/packages/contracts-bedrock/src/dispute/lib/LibUDT.sol +++ b/packages/contracts-bedrock/src/dispute/lib/LibUDT.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -import "src/dispute/lib/LibPosition.sol"; +// Libraries +import { Position } from "src/dispute/lib/LibPosition.sol"; using LibClaim for Claim global; using LibHash for Hash global; diff --git a/packages/contracts-bedrock/src/dispute/lib/Types.sol b/packages/contracts-bedrock/src/dispute/lib/Types.sol index 8d86e6e25387d..74106888fb059 100644 --- a/packages/contracts-bedrock/src/dispute/lib/Types.sol +++ b/packages/contracts-bedrock/src/dispute/lib/Types.sol @@ -1,7 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -import "src/dispute/lib/LibUDT.sol"; +// Libraries +import { + Position, + Hash, + GameType, + VMStatus, + Timestamp, + Duration, + Clock, + GameId, + Claim, + LibGameId, + LibClock +} from "src/dispute/lib/LibUDT.sol"; /// @notice The current status of the dispute game. enum GameStatus { diff --git a/packages/contracts-bedrock/src/governance/GovernanceToken.sol b/packages/contracts-bedrock/src/governance/GovernanceToken.sol index fe1073f4ce456..3843c3241b2b0 100644 --- a/packages/contracts-bedrock/src/governance/GovernanceToken.sol +++ b/packages/contracts-bedrock/src/governance/GovernanceToken.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +// Contracts +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import { ERC20Votes, ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; /// @custom:predeploy 0x4200000000000000000000000000000000000042 /// @title GovernanceToken diff --git a/packages/contracts-bedrock/src/governance/MintManager.sol b/packages/contracts-bedrock/src/governance/MintManager.sol index 57abeece6b62c..0f58e391c5121 100644 --- a/packages/contracts-bedrock/src/governance/MintManager.sol +++ b/packages/contracts-bedrock/src/governance/MintManager.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.15; // Contracts -import "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; // Interfaces -import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; +import { IGovernanceToken } from "interfaces/governance/IGovernanceToken.sol"; /// @title MintManager /// @notice Set as `owner` of the governance token and responsible for the token inflation diff --git a/packages/contracts-bedrock/src/legacy/AddressManager.sol b/packages/contracts-bedrock/src/legacy/AddressManager.sol index daaac412e0a00..e3b2e9274cc92 100644 --- a/packages/contracts-bedrock/src/legacy/AddressManager.sol +++ b/packages/contracts-bedrock/src/legacy/AddressManager.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; /// @custom:legacy true diff --git a/packages/contracts-bedrock/src/legacy/DeployerWhitelist.sol b/packages/contracts-bedrock/src/legacy/DeployerWhitelist.sol index a7a6313edf4b3..05a7798aeca3a 100644 --- a/packages/contracts-bedrock/src/legacy/DeployerWhitelist.sol +++ b/packages/contracts-bedrock/src/legacy/DeployerWhitelist.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @custom:legacy true /// @custom:proxied true @@ -41,8 +42,8 @@ contract DeployerWhitelist is ISemver { } /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; + /// @custom:semver 1.1.1-beta.3 + string public constant version = "1.1.1-beta.3"; /// @notice Adds or removes an address from the deployment whitelist. /// @param _deployer Address to update permissions for. diff --git a/packages/contracts-bedrock/src/legacy/L1BlockNumber.sol b/packages/contracts-bedrock/src/legacy/L1BlockNumber.sol index 19a595a3fad3c..6e0e33fa94878 100644 --- a/packages/contracts-bedrock/src/legacy/L1BlockNumber.sol +++ b/packages/contracts-bedrock/src/legacy/L1BlockNumber.sol @@ -5,8 +5,8 @@ pragma solidity 0.8.15; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; /// @custom:legacy true /// @custom:proxied true @@ -18,8 +18,8 @@ import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; /// contract instead. contract L1BlockNumber is ISemver { /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.2 - string public constant version = "1.1.1-beta.2"; + /// @custom:semver 1.1.1-beta.3 + string public constant version = "1.1.1-beta.3"; /// @notice Returns the L1 block number. receive() external payable { diff --git a/packages/contracts-bedrock/src/legacy/L1ChugSplashProxy.sol b/packages/contracts-bedrock/src/legacy/L1ChugSplashProxy.sol index 28125a23f90f4..a2a62707c6eea 100644 --- a/packages/contracts-bedrock/src/legacy/L1ChugSplashProxy.sol +++ b/packages/contracts-bedrock/src/legacy/L1ChugSplashProxy.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Libraries import { Constants } from "src/libraries/Constants.sol"; -import { IL1ChugSplashDeployer } from "src/legacy/interfaces/IL1ChugSplashProxy.sol"; + +// Interfaces +import { IL1ChugSplashDeployer } from "interfaces/legacy/IL1ChugSplashProxy.sol"; /// @custom:legacy true /// @title L1ChugSplashProxy diff --git a/packages/contracts-bedrock/src/legacy/LegacyMessagePasser.sol b/packages/contracts-bedrock/src/legacy/LegacyMessagePasser.sol index b90d1263f7a96..bdc81ad2839ec 100644 --- a/packages/contracts-bedrock/src/legacy/LegacyMessagePasser.sol +++ b/packages/contracts-bedrock/src/legacy/LegacyMessagePasser.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @custom:legacy true /// @custom:proxied true @@ -14,8 +15,8 @@ contract LegacyMessagePasser is ISemver { mapping(bytes32 => bool) public sentMessages; /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; + /// @custom:semver 1.1.1-beta.3 + string public constant version = "1.1.1-beta.3"; /// @notice Passes a message to L1. /// @param _message Message to pass to L1. diff --git a/packages/contracts-bedrock/src/legacy/LegacyMintableERC20.sol b/packages/contracts-bedrock/src/legacy/LegacyMintableERC20.sol index 2508b34e43b3f..8efd0bf0c4938 100644 --- a/packages/contracts-bedrock/src/legacy/LegacyMintableERC20.sol +++ b/packages/contracts-bedrock/src/legacy/LegacyMintableERC20.sol @@ -1,13 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Interfaces import { ILegacyMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; /// @title LegacyMintableERC20 /// @notice The legacy implementation of the OptimismMintableERC20. This /// contract is deprecated and should no longer be used. -contract LegacyMintableERC20 is ILegacyMintableERC20, ERC20 { +contract LegacyMintableERC20 is ERC20 { /// @notice Emitted when the token is minted by the bridge. event Mint(address indexed _account, uint256 _amount); diff --git a/packages/contracts-bedrock/src/legacy/ResolvedDelegateProxy.sol b/packages/contracts-bedrock/src/legacy/ResolvedDelegateProxy.sol index 3005456570ff6..d4e4a96f7ff1d 100644 --- a/packages/contracts-bedrock/src/legacy/ResolvedDelegateProxy.sol +++ b/packages/contracts-bedrock/src/legacy/ResolvedDelegateProxy.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { AddressManager } from "src/legacy/AddressManager.sol"; /// @custom:legacy true diff --git a/packages/contracts-bedrock/src/libraries/Arithmetic.sol b/packages/contracts-bedrock/src/libraries/Arithmetic.sol index dfd47274f7f96..140affaa718d5 100644 --- a/packages/contracts-bedrock/src/libraries/Arithmetic.sol +++ b/packages/contracts-bedrock/src/libraries/Arithmetic.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Libraries import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol"; import { FixedPointMathLib } from "@rari-capital/solmate/src/utils/FixedPointMathLib.sol"; diff --git a/packages/contracts-bedrock/src/libraries/Constants.sol b/packages/contracts-bedrock/src/libraries/Constants.sol index 1cbd61d21a5e0..6dcf3611956af 100644 --- a/packages/contracts-bedrock/src/libraries/Constants.sol +++ b/packages/contracts-bedrock/src/libraries/Constants.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +// Interfaces +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; /// @title Constants /// @notice Constants is a library for storing constants. Simple! Don't put everything in here, just diff --git a/packages/contracts-bedrock/src/libraries/Encoding.sol b/packages/contracts-bedrock/src/libraries/Encoding.sol index edcdd4ed75e2a..5aa4ee7d3d8a4 100644 --- a/packages/contracts-bedrock/src/libraries/Encoding.sol +++ b/packages/contracts-bedrock/src/libraries/Encoding.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Libraries import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { RLPWriter } from "src/libraries/rlp/RLPWriter.sol"; @@ -74,6 +75,7 @@ library Encoding { pure returns (bytes memory) { + // nosemgrep: sol-style-use-abi-encodecall return abi.encodeWithSignature("relayMessage(address,address,bytes,uint256)", _target, _sender, _data, _nonce); } @@ -97,6 +99,7 @@ library Encoding { pure returns (bytes memory) { + // nosemgrep: sol-style-use-abi-encodecall return abi.encodeWithSignature( "relayMessage(uint256,address,address,uint256,uint256,bytes)", _nonce, diff --git a/packages/contracts-bedrock/src/libraries/GasPayingToken.sol b/packages/contracts-bedrock/src/libraries/GasPayingToken.sol index 37c06840bd595..bf53367476b50 100644 --- a/packages/contracts-bedrock/src/libraries/GasPayingToken.sol +++ b/packages/contracts-bedrock/src/libraries/GasPayingToken.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Libraries +import { LibString } from "@solady/utils/LibString.sol"; import { Storage } from "src/libraries/Storage.sol"; import { Constants } from "src/libraries/Constants.sol"; -import { LibString } from "@solady/utils/LibString.sol"; /// @title IGasToken /// @notice Implemented by contracts that are aware of the custom gas token used diff --git a/packages/contracts-bedrock/src/libraries/Hashing.sol b/packages/contracts-bedrock/src/libraries/Hashing.sol index 0f0f15678f97c..b736ad9e4b7e7 100644 --- a/packages/contracts-bedrock/src/libraries/Hashing.sol +++ b/packages/contracts-bedrock/src/libraries/Hashing.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Libraries import { Types } from "src/libraries/Types.sol"; import { Encoding } from "src/libraries/Encoding.sol"; diff --git a/packages/contracts-bedrock/src/libraries/LegacyCrossDomainUtils.sol b/packages/contracts-bedrock/src/libraries/LegacyCrossDomainUtils.sol index 2a784ca1b6dca..a43bc2cecc4b3 100644 --- a/packages/contracts-bedrock/src/libraries/LegacyCrossDomainUtils.sol +++ b/packages/contracts-bedrock/src/libraries/LegacyCrossDomainUtils.sol @@ -24,6 +24,7 @@ library LegacyCrossDomainUtils { pure returns (bytes memory) { + // nosemgrep: sol-style-use-abi-encodecall return abi.encodeWithSignature( "relayMessage(address,address,bytes,uint256)", _target, _sender, _message, _messageNonce ); diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index c8fca4376bdef..9d10b9fa344d2 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.0; // This excludes the preinstalls (non-protocol contracts). library Predeploys { /// @notice Number of predeploy-namespace addresses reserved for protocol usage. - uint256 internal constant PREDEPLOY_COUNT = 2048; + uint256 internal constant PREDEPLOY_COUNT = 4096; /// @custom:legacy /// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated @@ -74,6 +74,9 @@ library Predeploys { /// @notice Address of the EAS predeploy. address internal constant EAS = 0x4200000000000000000000000000000000000021; + /// @notice Address of the SOUL_GAS_TOKEN predeploy. + address internal constant SOUL_GAS_TOKEN = 0x4200000000000000000000000000000000000800; + /// @notice Address of the GovernanceToken predeploy. address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042; @@ -105,6 +108,9 @@ library Predeploys { /// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract. address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28; + /// @notice Address of the SuperchainTokenBridge predeploy. + address internal constant SUPERCHAIN_TOKEN_BRIDGE = 0x4200000000000000000000000000000000000028; + /// @notice Returns the name of the predeploy at the given address. function getName(address _addr) internal pure returns (string memory out_) { require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); @@ -135,6 +141,8 @@ library Predeploys { if (_addr == ETH_LIQUIDITY) return "ETHLiquidity"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) return "OptimismSuperchainERC20Beacon"; + if (_addr == SUPERCHAIN_TOKEN_BRIDGE) return "SuperchainTokenBridge"; + if (_addr == SOUL_GAS_TOKEN) return "SoulGasToken"; revert("Predeploys: unnamed predeploy"); } @@ -151,14 +159,15 @@ library Predeploys { || _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER || _addr == OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == PROXY_ADMIN || _addr == BASE_FEE_VAULT || _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN - || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) - || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY) - || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) - || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON); + || _addr == SOUL_GAS_TOKEN || (_useInterop && _addr == CROSS_L2_INBOX) + || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) || (_useInterop && _addr == SUPERCHAIN_WETH) + || (_useInterop && _addr == ETH_LIQUIDITY) || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) + || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) + || (_useInterop && _addr == SUPERCHAIN_TOKEN_BRIDGE); } function isPredeployNamespace(address _addr) internal pure returns (bool) { - return uint160(_addr) >> 11 == uint160(0x4200000000000000000000000000000000000000) >> 11; + return uint160(_addr) >> 12 == uint160(0x4200000000000000000000000000000000000000) >> 12; } /// @notice Function to compute the expected address of the predeploy implementation diff --git a/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol b/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol index eee6cc699489e..30ce96972a191 100644 --- a/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol +++ b/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol @@ -12,3 +12,6 @@ error NotCustomGasToken(); /// @notice Error for when a transfer via call fails. error TransferFailed(); + +/// @notice Thrown when attempting to perform an operation and the account is the zero address. +error ZeroAddress(); diff --git a/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol b/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol index 9f82ca8c802ec..f342658cb45cf 100644 --- a/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol +++ b/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol @@ -1,7 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.8; -import "./RLPErrors.sol"; +// Libraries +import { + EmptyItem, + UnexpectedString, + InvalidDataRemainder, + ContentLengthMismatch, + InvalidHeader, + UnexpectedList +} from "src/libraries/rlp/RLPErrors.sol"; /// @custom:attribution https://github.com/hamdiallam/Solidity-RLP /// @title RLPReader diff --git a/packages/contracts-bedrock/src/libraries/trie/MerkleTrie.sol b/packages/contracts-bedrock/src/libraries/trie/MerkleTrie.sol index fa500894b165d..cbbf9fdf3d653 100644 --- a/packages/contracts-bedrock/src/libraries/trie/MerkleTrie.sol +++ b/packages/contracts-bedrock/src/libraries/trie/MerkleTrie.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { Bytes } from "../Bytes.sol"; -import { RLPReader } from "../rlp/RLPReader.sol"; +// Libraries +import { Bytes } from "src/libraries/Bytes.sol"; +import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; /// @title MerkleTrie /// @notice MerkleTrie is a small library for verifying standard Ethereum Merkle-Patricia trie diff --git a/packages/contracts-bedrock/src/libraries/trie/SecureMerkleTrie.sol b/packages/contracts-bedrock/src/libraries/trie/SecureMerkleTrie.sol index e8eab17917a12..56992fba99722 100644 --- a/packages/contracts-bedrock/src/libraries/trie/SecureMerkleTrie.sol +++ b/packages/contracts-bedrock/src/libraries/trie/SecureMerkleTrie.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { MerkleTrie } from "./MerkleTrie.sol"; +// Libraries +import { MerkleTrie } from "src/libraries/trie/MerkleTrie.sol"; /// @title SecureMerkleTrie /// @notice SecureMerkleTrie is a thin wrapper around the MerkleTrie library that hashes the input diff --git a/packages/contracts-bedrock/src/periphery/AssetReceiver.sol b/packages/contracts-bedrock/src/periphery/AssetReceiver.sol index ce59d8ef753e7..0ee2d52a29bc1 100644 --- a/packages/contracts-bedrock/src/periphery/AssetReceiver.sol +++ b/packages/contracts-bedrock/src/periphery/AssetReceiver.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Contracts import { ERC20 } from "@rari-capital/solmate/src/tokens/ERC20.sol"; import { ERC721 } from "@rari-capital/solmate/src/tokens/ERC721.sol"; -import { Transactor } from "./Transactor.sol"; +import { Transactor } from "src/periphery/Transactor.sol"; /// @title AssetReceiver /// @notice AssetReceiver is a minimal contract for receiving funds assets in the form of either diff --git a/packages/contracts-bedrock/src/periphery/Transactor.sol b/packages/contracts-bedrock/src/periphery/Transactor.sol index 01c8cc384ad1d..168367b4109e6 100644 --- a/packages/contracts-bedrock/src/periphery/Transactor.sol +++ b/packages/contracts-bedrock/src/periphery/Transactor.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Contracts import { Owned } from "@rari-capital/solmate/src/auth/Owned.sol"; /// @title Transactor diff --git a/packages/contracts-bedrock/src/periphery/TransferOnion.sol b/packages/contracts-bedrock/src/periphery/TransferOnion.sol index db473a1828ceb..292eb9c342327 100644 --- a/packages/contracts-bedrock/src/periphery/TransferOnion.sol +++ b/packages/contracts-bedrock/src/periphery/TransferOnion.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Libraries import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title TransferOnion diff --git a/packages/contracts-bedrock/src/periphery/drippie/Drippie.sol b/packages/contracts-bedrock/src/periphery/drippie/Drippie.sol index 014694813e46f..c7240d015a976 100644 --- a/packages/contracts-bedrock/src/periphery/drippie/Drippie.sol +++ b/packages/contracts-bedrock/src/periphery/drippie/Drippie.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { AssetReceiver } from "../AssetReceiver.sol"; -import { IDripCheck } from "./IDripCheck.sol"; +// Contracts +import { AssetReceiver } from "src/periphery/AssetReceiver.sol"; + +// Interfaces +import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol"; /// @title Drippie /// @notice Drippie is a system for managing automated contract interactions. A specific interaction diff --git a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckBalanceLow.sol b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckBalanceLow.sol index ff7121051c767..d889a4fa4422f 100644 --- a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckBalanceLow.sol +++ b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckBalanceLow.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IDripCheck } from "../IDripCheck.sol"; +// Interfaces +import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol"; /// @title CheckBalanceLow /// @notice DripCheck for checking if an account's balance is below a given threshold. diff --git a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckGelatoLow.sol b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckGelatoLow.sol index a5ef463f89e6e..a3c79b3fc3671 100644 --- a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckGelatoLow.sol +++ b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckGelatoLow.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IDripCheck } from "../IDripCheck.sol"; -import { IGelatoTreasury } from "src/vendor/interfaces/IGelatoTreasury.sol"; +// Interfaces +import { IGelatoTreasury } from "interfaces/vendor/IGelatoTreasury.sol"; +import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol"; /// @title CheckGelatoLow /// @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold. diff --git a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckSecrets.sol b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckSecrets.sol index 5cfc4251b4567..f255c2e6964ed 100644 --- a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckSecrets.sol +++ b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckSecrets.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IDripCheck } from "../IDripCheck.sol"; +// Interfaces +import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol"; /// @title CheckSecrets /// @notice DripCheck that checks if specific secrets exist (or not). Supports having a secret that diff --git a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckTrue.sol b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckTrue.sol index 1ce7138945c3f..18d4956f8e696 100644 --- a/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckTrue.sol +++ b/packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckTrue.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IDripCheck } from "../IDripCheck.sol"; +// Interfaces +import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol"; /// @title CheckTrue /// @notice DripCheck that always returns true. diff --git a/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol b/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol index cce6a83b3e131..66cab547317b2 100644 --- a/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol +++ b/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol @@ -1,10 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IFaucetAuthModule } from "src/periphery/faucet/authmodules/IFaucetAuthModule.sol"; -import { SafeCall } from "src/libraries/SafeCall.sol"; +// Contracts import { SafeSend } from "src/universal/SafeSend.sol"; +// Libraries +import { SafeCall } from "src/libraries/SafeCall.sol"; + +// Interfaces +import { IFaucetAuthModule } from "src/periphery/faucet/authmodules/IFaucetAuthModule.sol"; + /// @title Faucet /// @notice Faucet contract that drips ETH to users. contract Faucet { diff --git a/packages/contracts-bedrock/src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol b/packages/contracts-bedrock/src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol index 115810d9d1392..c9077943610ab 100644 --- a/packages/contracts-bedrock/src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol +++ b/packages/contracts-bedrock/src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import { IFaucetAuthModule } from "./IFaucetAuthModule.sol"; -import { Faucet } from "../Faucet.sol"; + +// Interfaces +import { IFaucetAuthModule } from "src/periphery/faucet/authmodules/IFaucetAuthModule.sol"; +import { Faucet } from "src/periphery/faucet/Faucet.sol"; /// @title AdminFaucetAuthModule /// @notice FaucetAuthModule that allows an admin to sign off on a given faucet drip. Takes an admin diff --git a/packages/contracts-bedrock/src/periphery/faucet/authmodules/IFaucetAuthModule.sol b/packages/contracts-bedrock/src/periphery/faucet/authmodules/IFaucetAuthModule.sol index a94071dd2befc..b4d91febf4a15 100644 --- a/packages/contracts-bedrock/src/periphery/faucet/authmodules/IFaucetAuthModule.sol +++ b/packages/contracts-bedrock/src/periphery/faucet/authmodules/IFaucetAuthModule.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { Faucet } from "../Faucet.sol"; +// Contracts +import { Faucet } from "src/periphery/faucet/Faucet.sol"; /// @title IFaucetAuthModule /// @notice Interface for faucet authentication modules. diff --git a/packages/contracts-bedrock/src/periphery/op-nft/AttestationStation.sol b/packages/contracts-bedrock/src/periphery/op-nft/AttestationStation.sol deleted file mode 100644 index 4d15862d43580..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/AttestationStation.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; - -/// @title AttestationStation -/// @author Optimism Collective -/// @author Gitcoin -/// @notice Where attestations live. -contract AttestationStation is ISemver { - /// @notice Struct representing data that is being attested. - /// @custom:field about Address for which the attestation is about. - /// @custom:field key A bytes32 key for the attestation. - /// @custom:field val The attestation as arbitrary bytes. - struct AttestationData { - address about; - bytes32 key; - bytes val; - } - - /// @notice Maps addresses to attestations. Creator => About => Key => Value. - mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations; - - /// @notice Emitted when Attestation is created. - /// @param creator Address that made the attestation. - /// @param about Address attestation is about. - /// @param key Key of the attestation. - /// @param val Value of the attestation. - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - /// @notice Semantic version. - /// @custom:semver 1.2.1-beta.1 - string public constant version = "1.2.1-beta.1"; - - /// @notice Allows anyone to create an attestation. - /// @param _about Address that the attestation is about. - /// @param _key A key used to namespace the attestation. - /// @param _val An arbitrary value stored as part of the attestation. - function attest(address _about, bytes32 _key, bytes memory _val) public { - attestations[msg.sender][_about][_key] = _val; - - emit AttestationCreated(msg.sender, _about, _key, _val); - } - - /// @notice Allows anyone to create attestations. - /// @param _attestations An array of AttestationData structs. - function attest(AttestationData[] calldata _attestations) external { - uint256 length = _attestations.length; - for (uint256 i = 0; i < length;) { - AttestationData memory attestation = _attestations[i]; - - attest(attestation.about, attestation.key, attestation.val); - - unchecked { - ++i; - } - } - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/Optimist.sol b/packages/contracts-bedrock/src/periphery/op-nft/Optimist.sol deleted file mode 100644 index b15c0f00044c6..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/Optimist.sol +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ERC721BurnableUpgradeable } from - "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; - -/// @author Optimism Collective -/// @author Gitcoin -/// @title Optimist -/// @notice A Soul Bound Token for real humans only(tm). -contract Optimist is ERC721BurnableUpgradeable, ISemver { - /// @notice Attestation key used by the attestor to attest the baseURI. - bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri"); - - /// @notice Attestor who attests to baseURI. - address public immutable BASE_URI_ATTESTOR; - - /// @notice Address of the AttestationStation contract. - AttestationStation public immutable ATTESTATION_STATION; - - /// @notice Address of the OptimistAllowlist contract. - OptimistAllowlist public immutable OPTIMIST_ALLOWLIST; - - /// @notice Semantic version. - /// @custom:semver 2.1.1-beta.1 - string public constant version = "2.1.1-beta.1"; - - /// @param _name Token name. - /// @param _symbol Token symbol. - /// @param _baseURIAttestor Address of the baseURI attestor. - /// @param _attestationStation Address of the AttestationStation contract. - /// @param _optimistAllowlist Address of the OptimistAllowlist contract - constructor( - string memory _name, - string memory _symbol, - address _baseURIAttestor, - AttestationStation _attestationStation, - OptimistAllowlist _optimistAllowlist - ) { - BASE_URI_ATTESTOR = _baseURIAttestor; - ATTESTATION_STATION = _attestationStation; - OPTIMIST_ALLOWLIST = _optimistAllowlist; - initialize(_name, _symbol); - } - - /// @notice Initializes the Optimist contract. - /// @param _name Token name. - /// @param _symbol Token symbol. - function initialize(string memory _name, string memory _symbol) public initializer { - __ERC721_init(_name, _symbol); - __ERC721Burnable_init(); - } - - /// @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation - /// of the recipient's address. Recipients must be permitted to mint, eventually anyone - /// will be able to mint. One token per address. - /// @param _recipient Address of the token recipient. - function mint(address _recipient) public { - require(isOnAllowList(_recipient), "Optimist: address is not on allowList"); - _safeMint(_recipient, tokenIdOfAddress(_recipient)); - } - - /// @notice Returns the baseURI for all tokens. - /// @return uri_ BaseURI for all tokens. - function baseURI() public view returns (string memory uri_) { - uri_ = string( - abi.encodePacked( - ATTESTATION_STATION.attestations(BASE_URI_ATTESTOR, address(this), bytes32("optimist.base-uri")) - ) - ); - } - - /// @notice Returns the token URI for a given token by ID - /// @param _tokenId Token ID to query. - /// @return uri_ Token URI for the given token by ID. - function tokenURI(uint256 _tokenId) public view virtual override returns (string memory uri_) { - uri_ = string( - abi.encodePacked( - baseURI(), - "/", - // Properly format the token ID as a 20 byte hex string (address). - Strings.toHexString(_tokenId, 20), - ".json" - ) - ); - } - - /// @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint - /// the Optimist NFT. Since the Optimist NFT will also be used as part of the - /// Citizens House, mints are currently restricted. Eventually anyone will be able - /// to mint. - /// @return allowed_ Whether or not the address is allowed to mint yet. - function isOnAllowList(address _recipient) public view returns (bool allowed_) { - allowed_ = OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient); - } - - /// @notice Returns the token ID for the token owned by a given address. This is the uint256 - /// representation of the given address. - /// @return Token ID for the token owned by the given address. - function tokenIdOfAddress(address _owner) public pure returns (uint256) { - return uint256(uint160(_owner)); - } - - /// @notice Disabled for the Optimist NFT (Soul Bound Token). - function approve(address, uint256) public pure override { - revert("Optimist: soul bound token"); - } - - /// @notice Disabled for the Optimist NFT (Soul Bound Token). - function setApprovalForAll(address, bool) public virtual override { - revert("Optimist: soul bound token"); - } - - /// @notice Prevents transfers of the Optimist NFT (Soul Bound Token). - /// @param _from Address of the token sender. - /// @param _to Address of the token recipient. - function _beforeTokenTransfer(address _from, address _to, uint256) internal virtual override { - require(_from == address(0) || _to == address(0), "Optimist: soul bound token"); - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/OptimistAllowlist.sol b/packages/contracts-bedrock/src/periphery/op-nft/OptimistAllowlist.sol deleted file mode 100644 index ffa46116a4c59..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/OptimistAllowlist.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; - -/// @title OptimistAllowlist -/// @notice Source of truth for whether an address is able to mint an Optimist NFT. -/// isAllowedToMint function checks various signals to return boolean value -/// for whether an address is eligible or not. -contract OptimistAllowlist is ISemver { - /// @notice Attestation key used by the AllowlistAttestor to manually add addresses to the - /// allowlist. - bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint"); - - /// @notice Attestation key used by Coinbase to issue attestations for Quest participants. - bytes32 public constant COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY = bytes32("coinbase.quest-eligible"); - - /// @notice Address of the AttestationStation contract. - AttestationStation public immutable ATTESTATION_STATION; - - /// @notice Attestor that issues 'optimist.can-mint' attestations. - address public immutable ALLOWLIST_ATTESTOR; - - /// @notice Attestor that issues 'coinbase.quest-eligible' attestations. - address public immutable COINBASE_QUEST_ATTESTOR; - - /// @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite' - /// attestations. - address public immutable OPTIMIST_INVITER; - - /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; - - /// @param _attestationStation Address of the AttestationStation contract. - /// @param _allowlistAttestor Address of the allowlist attestor. - /// @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor. - /// @param _optimistInviter Address of the OptimistInviter contract. - constructor( - AttestationStation _attestationStation, - address _allowlistAttestor, - address _coinbaseQuestAttestor, - address _optimistInviter - ) { - ATTESTATION_STATION = _attestationStation; - ALLOWLIST_ATTESTOR = _allowlistAttestor; - COINBASE_QUEST_ATTESTOR = _coinbaseQuestAttestor; - OPTIMIST_INVITER = _optimistInviter; - } - - /// @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the - /// Optimist NFT will also be used as part of the Citizens House, mints are currently - /// restricted. Eventually anyone will be able to mint. - /// Currently, address is allowed to mint if it satisfies any of the following: - /// 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor. - /// 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor - /// 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter - /// contract. - /// @param _claimer Address to check. - /// @return allowed_ Whether or not the address is allowed to mint yet. - function isAllowedToMint(address _claimer) public view returns (bool allowed_) { - allowed_ = _hasAttestationFromAllowlistAttestor(_claimer) || _hasAttestationFromCoinbaseQuestAttestor(_claimer) - || _hasAttestationFromOptimistInviter(_claimer); - } - - /// @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the - /// allowlist attestor. - /// @param _claimer Address to check. - /// @return valid_ Whether or not the address has a valid attestation. - function _hasAttestationFromAllowlistAttestor(address _claimer) internal view returns (bool valid_) { - // Expected attestation value is bytes32("true") - valid_ = _hasValidAttestation(ALLOWLIST_ATTESTOR, _claimer, OPTIMIST_CAN_MINT_ATTESTATION_KEY); - } - - /// @notice Checks whether an address has a valid attestation from the Coinbase attestor. - /// @param _claimer Address to check. - /// @return valid_ Whether or not the address has a valid attestation. - function _hasAttestationFromCoinbaseQuestAttestor(address _claimer) internal view returns (bool valid_) { - // Expected attestation value is bytes32("true") - valid_ = _hasValidAttestation(COINBASE_QUEST_ATTESTOR, _claimer, COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY); - } - - /// @notice Checks whether an address has a valid attestation from the OptimistInviter contract. - /// @param _claimer Address to check. - /// @return valid_ Whether or not the address has a valid attestation. - function _hasAttestationFromOptimistInviter(address _claimer) internal view returns (bool valid_) { - // Expected attestation value is the inviter's address - valid_ = _hasValidAttestation( - OPTIMIST_INVITER, _claimer, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY - ); - } - - /// @notice Checks whether an address has a valid truthy attestation. - /// Any attestation val other than bytes32("") is considered truthy. - /// @param _creator Address that made the attestation. - /// @param _about Address attestation is about. - /// @param _key Key of the attestation. - /// @return valid_ Whether or not the address has a valid truthy attestation. - function _hasValidAttestation(address _creator, address _about, bytes32 _key) internal view returns (bool valid_) { - valid_ = ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0; - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/OptimistInviter.sol b/packages/contracts-bedrock/src/periphery/op-nft/OptimistInviter.sol deleted file mode 100644 index ae0ab9d92657c..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/OptimistInviter.sol +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; - -/// @custom:upgradeable -/// @title OptimistInviter -/// @notice OptimistInviter issues "optimist.can-invite" and "optimist.can-mint-from-invite" -/// attestations. Accounts that have invites can issue signatures that allow other -/// accounts to claim an invite. The invitee uses a claim and reveal flow to claim the -/// invite to an address of their choosing. -/// -/// Parties involved: -/// 1) INVITE_GRANTER: trusted account that can allow accounts to issue invites -/// 2) issuer: account that is allowed to issue invites -/// 3) claimer: account that receives the invites -/// -/// Flow: -/// 1) INVITE_GRANTER calls _setInviteCount to allow an issuer to issue a certain number -/// of invites, and also creates a "optimist.can-invite" attestation for the issuer -/// 2) Off-chain, the issuer signs (EIP-712) a ClaimableInvite to produce a signature -/// 3) Off-chain, invite issuer sends the plaintext ClaimableInvite and the signature -/// to the recipient -/// 4) claimer chooses an address they want to receive the invite on -/// 5) claimer commits the hash of the address they want to receive the invite on and the -/// received signature keccak256(abi.encode(addressToReceiveTo, receivedSignature)) -/// using the commitInvite function -/// 6) claimer waits for the MIN_COMMITMENT_PERIOD to pass. -/// 7) claimer reveals the plaintext ClaimableInvite and the signature using the -/// claimInvite function, receiving the "optimist.can-mint-from-invite" attestation -contract OptimistInviter is ISemver, EIP712Upgradeable { - /// @notice Emitted when an invite is claimed. - /// @param issuer Address that issued the signature. - /// @param claimer Address that claimed the invite. - event InviteClaimed(address indexed issuer, address indexed claimer); - - /// @notice Version used for the EIP712 domain separator. This version is separated from the - /// contract semver because the EIP712 domain separator is used to sign messages, and - /// changing the domain separator invalidates all existing signatures. We should only - /// bump this version if we make a major change to the signature scheme. - string public constant EIP712_VERSION = "1.0.0"; - - /// @notice EIP712 typehash for the ClaimableInvite type. - bytes32 public constant CLAIMABLE_INVITE_TYPEHASH = keccak256("ClaimableInvite(address issuer,bytes32 nonce)"); - - /// @notice Attestation key for that signals that an account was allowed to issue invites - bytes32 public constant CAN_INVITE_ATTESTATION_KEY = bytes32("optimist.can-invite"); - - /// @notice Granter who can set accounts' invite counts. - address public immutable INVITE_GRANTER; - - /// @notice Address of the AttestationStation contract. - AttestationStation public immutable ATTESTATION_STATION; - - /// @notice Minimum age of a commitment (in seconds) before it can be revealed using - /// claimInvite. Currently set to 60 seconds. - /// - /// Prevents an attacker from front-running a commitment by taking the signature in the - /// claimInvite call and quickly committing and claiming it before the the claimer's - /// transaction succeeds. With this, frontrunning a commitment requires that an attacker - /// be able to prevent the honest claimer's claimInvite transaction from being included - /// for this long. - uint256 public constant MIN_COMMITMENT_PERIOD = 60; - - /// @notice Struct that represents a claimable invite that will be signed by the issuer. - /// @custom:field issuer Address that issued the signature. Reason this is explicitly included, - /// and not implicitly assumed to be the recovered address from the - /// signature is that the issuer may be using a ERC-1271 compatible - /// contract wallet, where the recovered address is not the same as the - /// issuer, or the signature is not an ECDSA signature at all. - /// @custom:field nonce Pseudorandom nonce to prevent replay attacks. - struct ClaimableInvite { - address issuer; - bytes32 nonce; - } - - /// @notice Maps from hashes to the timestamp when they were committed. - mapping(bytes32 => uint256) public commitmentTimestamps; - - /// @notice Maps from addresses to nonces to whether or not they have been used. - mapping(address => mapping(bytes32 => bool)) public usedNonces; - - /// @notice Maps from addresses to number of invites they have. - mapping(address => uint256) public inviteCounts; - - /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; - - /// @param _inviteGranter Address of the invite granter. - /// @param _attestationStation Address of the AttestationStation contract. - constructor(address _inviteGranter, AttestationStation _attestationStation) { - INVITE_GRANTER = _inviteGranter; - ATTESTATION_STATION = _attestationStation; - } - - /// @notice Initializes this contract, setting the EIP712 context. - /// Only update the EIP712_VERSION when there is a change to the signature scheme. - /// After the EIP712 version is changed, any signatures issued off-chain but not - /// claimed yet will no longer be accepted by the claimInvite function. Please make - /// sure to notify the issuers that they must re-issue their invite signatures. - /// @param _name Contract name. - function initialize(string memory _name) public initializer { - __EIP712_init(_name, EIP712_VERSION); - } - - /// @notice Allows invite granter to set the number of invites an address has. - /// @param _accounts An array of accounts to update the invite counts of. - /// @param _inviteCount Number of invites to set to. - function setInviteCounts(address[] calldata _accounts, uint256 _inviteCount) public { - // Only invite granter can grant invites - require(msg.sender == INVITE_GRANTER, "OptimistInviter: only invite granter can grant invites"); - - uint256 length = _accounts.length; - - AttestationStation.AttestationData[] memory attestations = new AttestationStation.AttestationData[](length); - - for (uint256 i; i < length;) { - // Set invite count for account to _inviteCount - inviteCounts[_accounts[i]] = _inviteCount; - - // Create an attestation for posterity that the account is allowed to create invites - attestations[i] = AttestationStation.AttestationData({ - about: _accounts[i], - key: CAN_INVITE_ATTESTATION_KEY, - val: bytes("true") - }); - - unchecked { - ++i; - } - } - - ATTESTATION_STATION.attest(attestations); - } - - /// @notice Allows anyone (but likely the claimer) to commit a received signature along with the - /// address to claim to. - /// - /// Before calling this function, the claimer should have received a signature from the - /// issuer off-chain. The claimer then calls this function with the hash of the - /// claimer's address and the received signature. This is necessary to prevent - /// front-running when the invitee is claiming the invite. Without a commit and reveal - /// scheme, anyone who is watching the mempool can take the signature being submitted - /// and front run the transaction to claim the invite to their own address. - /// - /// The same commitment can only be made once, and the function reverts if the - /// commitment has already been made. This prevents griefing where a malicious party can - /// prevent the original claimer from being able to claimInvite. - /// @param _commitment A hash of the claimer and signature concatenated. - /// keccak256(abi.encode(_claimer, _signature)) - function commitInvite(bytes32 _commitment) public { - // Check that the commitment hasn't already been made. This prevents griefing where - // a malicious party continuously re-submits the same commitment, preventing the original - // claimer from claiming their invite by resetting the minimum commitment period. - require(commitmentTimestamps[_commitment] == 0, "OptimistInviter: commitment already made"); - - commitmentTimestamps[_commitment] = block.timestamp; - } - - /// @notice Allows anyone to reveal a commitment and claim an invite. - /// The hash, keccak256(abi.encode(_claimer, _signature)), should have been already - /// committed using commitInvite. Before issuing the "optimist.can-mint-from-invite" - /// attestation, this function checks that - /// 1) the hash corresponding to the _claimer and the _signature was committed - /// 2) MIN_COMMITMENT_PERIOD has passed since the commitment was made. - /// 3) the _signature is signed correctly by the issuer - /// 4) the _signature hasn't already been used to claim an invite before - /// 5) the _signature issuer has not used up all of their invites - /// This function doesn't require that the _claimer is calling this function. - /// @param _claimer Address that will be granted the invite. - /// @param _claimableInvite ClaimableInvite struct containing the issuer and nonce. - /// @param _signature Signature signed over the claimable invite. - function claimInvite(address _claimer, ClaimableInvite calldata _claimableInvite, bytes memory _signature) public { - uint256 commitmentTimestamp = commitmentTimestamps[keccak256(abi.encode(_claimer, _signature))]; - - // Make sure the claimer and signature have been committed. - require(commitmentTimestamp > 0, "OptimistInviter: claimer and signature have not been committed yet"); - - // Check that MIN_COMMITMENT_PERIOD has passed since the commitment was made. - require( - commitmentTimestamp + MIN_COMMITMENT_PERIOD <= block.timestamp, - "OptimistInviter: minimum commitment period has not elapsed yet" - ); - - // Generate a EIP712 typed data hash to compare against the signature. - bytes32 digest = _hashTypedDataV4( - keccak256(abi.encode(CLAIMABLE_INVITE_TYPEHASH, _claimableInvite.issuer, _claimableInvite.nonce)) - ); - - // Uses SignatureChecker, which supports both regular ECDSA signatures from EOAs as well as - // ERC-1271 signatures from contract wallets or multi-sigs. This means that if the issuer - // wants to revoke a signature, they can use a smart contract wallet to issue the signature, - // then invalidate the signature after issuing it. - require( - SignatureChecker.isValidSignatureNow(_claimableInvite.issuer, digest, _signature), - "OptimistInviter: invalid signature" - ); - - // The issuer's signature commits to a nonce to prevent replay attacks. - // This checks that the nonce has not been used for this issuer before. The nonces are - // scoped to the issuer address, so the same nonce can be used by different issuers without - // clashing. - require( - usedNonces[_claimableInvite.issuer][_claimableInvite.nonce] == false, - "OptimistInviter: nonce has already been used" - ); - - // Set the nonce as used for the issuer so that it cannot be replayed. - usedNonces[_claimableInvite.issuer][_claimableInvite.nonce] = true; - - // Failing this check means that the issuer has used up all of their existing invites. - require(inviteCounts[_claimableInvite.issuer] > 0, "OptimistInviter: issuer has no invites"); - - // Reduce the issuer's invite count by 1. Can be unchecked because we check above that - // count is > 0. - unchecked { - --inviteCounts[_claimableInvite.issuer]; - } - - // Create the attestation that the claimer can mint from the issuer's invite. - // The invite issuer is included in the data of the attestation. - ATTESTATION_STATION.attest( - _claimer, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(_claimableInvite.issuer) - ); - - emit InviteClaimed(_claimableInvite.issuer, _claimer); - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/libraries/OptimistConstants.sol b/packages/contracts-bedrock/src/periphery/op-nft/libraries/OptimistConstants.sol deleted file mode 100644 index 225f778894954..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/libraries/OptimistConstants.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/// @title OptimistConstants -/// @notice Library for storing Optimist related constants that are shared in multiple contracts. -library OptimistConstants { - /// @notice Attestation key issued by OptimistInviter allowing the attested account to mint. - bytes32 internal constant OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY = bytes32("optimist.can-mint-from-invite"); -} diff --git a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol index 0882910d50721..a742c452ef0ba 100644 --- a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol +++ b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol @@ -7,15 +7,15 @@ import { Enum } from "safe-contracts/common/Enum.sol"; // Libraries import { Unauthorized } from "src/libraries/PortalErrors.sol"; -import "src/dispute/lib/Types.sol"; +import { GameType, Timestamp } from "src/dispute/lib/Types.sol"; // Interfaces -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title DeputyGuardianModule /// @notice This module is intended to be enabled on the Security Council Safe, which will own the Guardian role in the @@ -48,8 +48,8 @@ contract DeputyGuardianModule is ISemver { address internal immutable DEPUTY_GUARDIAN; /// @notice Semantic version. - /// @custom:semver 2.0.1-beta.3 - string public constant version = "2.0.1-beta.3"; + /// @custom:semver 2.0.1-beta.5 + string public constant version = "2.0.1-beta.5"; // Constructor to initialize the Safe and baseModule instances constructor(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) { diff --git a/packages/contracts-bedrock/src/safe/LivenessGuard.sol b/packages/contracts-bedrock/src/safe/LivenessGuard.sol index aa9a231a4b259..46c0072f7ba47 100644 --- a/packages/contracts-bedrock/src/safe/LivenessGuard.sol +++ b/packages/contracts-bedrock/src/safe/LivenessGuard.sol @@ -1,12 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Safe import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; import { Guard as BaseGuard } from "safe-contracts/base/GuardManager.sol"; -import { SafeSigners } from "src/safe/SafeSigners.sol"; import { Enum } from "safe-contracts/common/Enum.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Libraries import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { SafeSigners } from "src/safe/SafeSigners.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title LivenessGuard /// @notice This Guard contract is used to track the liveness of Safe owners. @@ -25,8 +30,8 @@ contract LivenessGuard is ISemver, BaseGuard { event OwnerRecorded(address owner); /// @notice Semantic version. - /// @custom:semver 1.0.1-beta.2 - string public constant version = "1.0.1-beta.2"; + /// @custom:semver 1.0.1-beta.4 + string public constant version = "1.0.1-beta.4"; /// @notice The safe account for which this contract will be the guard. Safe internal immutable SAFE; diff --git a/packages/contracts-bedrock/src/safe/LivenessModule.sol b/packages/contracts-bedrock/src/safe/LivenessModule.sol index cd41c6e2dd53a..a033507176cc5 100644 --- a/packages/contracts-bedrock/src/safe/LivenessModule.sol +++ b/packages/contracts-bedrock/src/safe/LivenessModule.sol @@ -1,11 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Safe import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; import { Enum } from "safe-contracts/common/Enum.sol"; import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; + +// Contracts import { LivenessGuard } from "src/safe/LivenessGuard.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title LivenessModule /// @notice This module is intended to be used in conjunction with the LivenessGuard. In the event @@ -53,8 +58,8 @@ contract LivenessModule is ISemver { uint256 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; /// @notice Semantic version. - /// @custom:semver 1.2.1-beta.1 - string public constant version = "1.2.1-beta.1"; + /// @custom:semver 1.2.1-beta.3 + string public constant version = "1.2.1-beta.3"; // Constructor to initialize the Safe and baseModule instances constructor( diff --git a/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol b/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol index 66c724d3eba93..85d801f0a1a6c 100644 --- a/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Libraries import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; import { Hashing } from "src/libraries/Hashing.sol"; diff --git a/packages/contracts-bedrock/src/universal/ERC721Bridge.sol b/packages/contracts-bedrock/src/universal/ERC721Bridge.sol index 52217fab713cb..c989da56c7b56 100644 --- a/packages/contracts-bedrock/src/universal/ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/universal/ERC721Bridge.sol @@ -1,10 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +// Contracts import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +// Libraries +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; + +// Interfaces +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; + /// @title ERC721Bridge /// @notice ERC721Bridge is a base contract for the L1 and L2 ERC721 bridges. abstract contract ERC721Bridge is Initializable { diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol index 838a21af56626..62aaf45b2fc37 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol @@ -1,20 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { ILegacyMintableERC20, IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Libraries import { Preinstalls } from "src/libraries/Preinstalls.sol"; +// Interfaces +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IOptimismMintableERC20 } from "interfaces/universal/IOptimismMintableERC20.sol"; +import { ILegacyMintableERC20 } from "interfaces/universal/ILegacyMintableERC20.sol"; + /// @title OptimismMintableERC20 /// @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed /// to allow the StandardBridge contracts to mint and burn tokens. This makes it possible to /// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa. /// Designed to be backwards compatible with the older StandardL2ERC20 token which was only /// meant for use on L2. -contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20Permit, ISemver { +contract OptimismMintableERC20 is ERC20Permit, ISemver { /// @notice Address of the corresponding version of this token on the remote chain. address public immutable REMOTE_TOKEN; @@ -41,8 +47,8 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, } /// @notice Semantic version. - /// @custom:semver 1.4.0-beta.1 - string public constant version = "1.4.0-beta.1"; + /// @custom:semver 1.4.0-beta.4 + string public constant version = "1.4.0-beta.4"; /// @notice Getter function for the permit2 address. It deterministically deployed /// so it will always be at the same address. It is also included as a preinstall, @@ -96,15 +102,7 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, /// @notice Allows the StandardBridge on this network to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function mint( - address _to, - uint256 _amount - ) - external - virtual - override(IOptimismMintableERC20, ILegacyMintableERC20) - onlyBridge - { + function mint(address _to, uint256 _amount) external virtual onlyBridge { _mint(_to, _amount); emit Mint(_to, _amount); } @@ -112,15 +110,7 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, /// @notice Allows the StandardBridge on this network to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function burn( - address _from, - uint256 _amount - ) - external - virtual - override(IOptimismMintableERC20, ILegacyMintableERC20) - onlyBridge - { + function burn(address _from, uint256 _amount) external virtual onlyBridge { _burn(_from, _amount); emit Burn(_from, _amount); } diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol index e179c6408dad1..62d2de8e7f3c1 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Contracts import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IOptimismERC20Factory } from "interfaces/L2/IOptimismERC20Factory.sol"; /// @custom:proxied true /// @custom:predeployed 0x4200000000000000000000000000000000000012 @@ -48,8 +51,8 @@ contract OptimismMintableERC20Factory is ISemver, Initializable, IOptimismERC20F /// the OptimismMintableERC20 token contract since this contract /// is responsible for deploying OptimismMintableERC20 contracts. /// @notice Semantic version. - /// @custom:semver 1.10.1-beta.3 - string public constant version = "1.10.1-beta.3"; + /// @custom:semver 1.10.1-beta.6 + string public constant version = "1.10.1-beta.6"; /// @notice Constructs the OptimismMintableERC20Factory contract. constructor() { diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC721.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC721.sol index 955a340722051..63d4eb29a140f 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC721.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC721.sol @@ -1,25 +1,39 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { ERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +// Libraries import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { IOptimismMintableERC721 } from "src/universal/interfaces/IOptimismMintableERC721.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IOptimismMintableERC721 } from "interfaces/universal/IOptimismMintableERC721.sol"; /// @title OptimismMintableERC721 /// @notice This contract is the remote representation for some token that lives on another network, /// typically an Optimism representation of an Ethereum-based token. Standard reference /// implementation that can be extended or modified according to your needs. -contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, ISemver { - /// @inheritdoc IOptimismMintableERC721 +contract OptimismMintableERC721 is ERC721Enumerable, ISemver { + /// @notice Emitted when a token is minted. + /// @param account Address of the account the token was minted to. + /// @param tokenId Token ID of the minted token. + event Mint(address indexed account, uint256 tokenId); + + /// @notice Emitted when a token is burned. + /// @param account Address of the account the token was burned from. + /// @param tokenId Token ID of the burned token. + event Burn(address indexed account, uint256 tokenId); + + /// @notice Chain ID of the chain where the remote token is deployed. uint256 public immutable REMOTE_CHAIN_ID; - /// @inheritdoc IOptimismMintableERC721 + /// @notice Address of the token on the remote domain. address public immutable REMOTE_TOKEN; - /// @inheritdoc IOptimismMintableERC721 + /// @notice Address of the ERC721 bridge on this network. address public immutable BRIDGE; /// @notice Base token URI for this token. @@ -32,8 +46,8 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, IS } /// @notice Semantic version. - /// @custom:semver 1.3.1-beta.2 - string public constant version = "1.3.1-beta.2"; + /// @custom:semver 1.3.1-beta.5 + string public constant version = "1.3.1-beta.5"; /// @param _bridge Address of the bridge on this network. /// @param _remoteChainId Chain ID where the remote token is deployed. @@ -70,29 +84,34 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, IS ); } - /// @inheritdoc IOptimismMintableERC721 + /// @notice Chain ID of the chain where the remote token is deployed. function remoteChainId() external view returns (uint256) { return REMOTE_CHAIN_ID; } - /// @inheritdoc IOptimismMintableERC721 + /// @notice Address of the token on the remote domain. function remoteToken() external view returns (address) { return REMOTE_TOKEN; } - /// @inheritdoc IOptimismMintableERC721 + /// @notice Address of the ERC721 bridge on this network. function bridge() external view returns (address) { return BRIDGE; } - /// @inheritdoc IOptimismMintableERC721 + /// @notice Mints some token ID for a user, checking first that contract recipients + /// are aware of the ERC721 protocol to prevent tokens from being forever locked. + /// @param _to Address of the user to mint the token for. + /// @param _tokenId Token ID to mint. function safeMint(address _to, uint256 _tokenId) external virtual onlyBridge { _safeMint(_to, _tokenId); emit Mint(_to, _tokenId); } - /// @inheritdoc IOptimismMintableERC721 + /// @notice Burns a token ID from a user. + /// @param _from Address of the user to burn the token from. + /// @param _tokenId Token ID to burn. function burn(address _from, uint256 _tokenId) external virtual onlyBridge { _burn(_tokenId); @@ -102,7 +121,7 @@ contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, IS /// @notice Checks if a given interface ID is supported by this contract. /// @param _interfaceId The interface ID to check. /// @return True if the interface ID is supported, false otherwise. - function supportsInterface(bytes4 _interfaceId) public view override(ERC721Enumerable, IERC165) returns (bool) { + function supportsInterface(bytes4 _interfaceId) public view override(ERC721Enumerable) returns (bool) { bytes4 iface = type(IOptimismMintableERC721).interfaceId; return _interfaceId == iface || super.supportsInterface(_interfaceId); } diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC721Factory.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC721Factory.sol index 943859a7db1cc..aa137378fccd0 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC721Factory.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC721Factory.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Contracts import { OptimismMintableERC721 } from "src/universal/OptimismMintableERC721.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title OptimismMintableERC721Factory /// @notice Factory contract for creating OptimismMintableERC721 contracts. @@ -25,8 +28,8 @@ contract OptimismMintableERC721Factory is ISemver { event OptimismMintableERC721Created(address indexed localToken, address indexed remoteToken, address deployer); /// @notice Semantic version. - /// @custom:semver 1.4.1-beta.3 - string public constant version = "1.4.1-beta.3"; + /// @custom:semver 1.4.1-beta.6 + string public constant version = "1.4.1-beta.6"; /// @notice The semver MUST be bumped any time that there is a change in /// the OptimismMintableERC721 token contract since this contract diff --git a/packages/contracts-bedrock/src/universal/Proxy.sol b/packages/contracts-bedrock/src/universal/Proxy.sol index b50b08efca839..4fd53dae06ba2 100644 --- a/packages/contracts-bedrock/src/universal/Proxy.sol +++ b/packages/contracts-bedrock/src/universal/Proxy.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Libraries import { Constants } from "src/libraries/Constants.sol"; /// @title Proxy diff --git a/packages/contracts-bedrock/src/universal/ProxyAdmin.sol b/packages/contracts-bedrock/src/universal/ProxyAdmin.sol index dec119398c0fa..9e7cd9082428d 100644 --- a/packages/contracts-bedrock/src/universal/ProxyAdmin.sol +++ b/packages/contracts-bedrock/src/universal/ProxyAdmin.sol @@ -8,11 +8,11 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Constants } from "src/libraries/Constants.sol"; // Interfaces -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IL1ChugSplashProxy } from "src/legacy/interfaces/IL1ChugSplashProxy.sol"; -import { IStaticL1ChugSplashProxy } from "src/legacy/interfaces/IL1ChugSplashProxy.sol"; -import { IStaticERC1967Proxy } from "src/universal/interfaces/IStaticERC1967Proxy.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; +import { IStaticL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; +import { IStaticERC1967Proxy } from "interfaces/universal/IStaticERC1967Proxy.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; /// @title ProxyAdmin /// @notice This is an auxiliary contract meant to be assigned as the admin of an ERC1967 Proxy, diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index 476d3ba54c933..ff01560ad1a82 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -1,17 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +// Contracts +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +// Libraries import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; -import { IOptimismMintableERC20, ILegacyMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { Constants } from "src/libraries/Constants.sol"; +// Interfaces +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IOptimismMintableERC20 } from "interfaces/universal/IOptimismMintableERC20.sol"; +import { ILegacyMintableERC20 } from "interfaces/universal/ILegacyMintableERC20.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; + /// @custom:upgradeable /// @title StandardBridge /// @notice StandardBridge is a base contract for the L1 and L2 standard ERC20 bridges. It handles @@ -293,7 +298,7 @@ abstract contract StandardBridge is Initializable { "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" ); - OptimismMintableERC20(_localToken).mint(_to, _amount); + IOptimismMintableERC20(_localToken).mint(_to, _amount); } else { deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; IERC20(_localToken).safeTransfer(_to, _amount); @@ -363,7 +368,7 @@ abstract contract StandardBridge is Initializable { "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" ); - OptimismMintableERC20(_localToken).burn(_from, _amount); + IOptimismMintableERC20(_localToken).burn(_from, _amount); } else { IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; diff --git a/packages/contracts-bedrock/src/universal/StorageSetter.sol b/packages/contracts-bedrock/src/universal/StorageSetter.sol index 5bd53a75b3667..9656ca21c5d2d 100644 --- a/packages/contracts-bedrock/src/universal/StorageSetter.sol +++ b/packages/contracts-bedrock/src/universal/StorageSetter.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +// Libraries import { Storage } from "src/libraries/Storage.sol"; +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + /// @title StorageSetter /// @notice A simple contract that allows setting arbitrary storage slots. /// WARNING: this contract is not safe to be called by untrusted parties. @@ -16,8 +19,8 @@ contract StorageSetter is ISemver { } /// @notice Semantic version. - /// @custom:semver 1.2.1-beta.2 - string public constant version = "1.2.1-beta.2"; + /// @custom:semver 1.2.1-beta.4 + string public constant version = "1.2.1-beta.4"; /// @notice Stores a bytes32 `_value` at `_slot`. Any storage slots that /// are packed should be set through this interface. diff --git a/packages/contracts-bedrock/src/universal/WETH98.sol b/packages/contracts-bedrock/src/universal/WETH98.sol index 5665e5f9210fe..c2909e6fa6f70 100644 --- a/packages/contracts-bedrock/src/universal/WETH98.sol +++ b/packages/contracts-bedrock/src/universal/WETH98.sol @@ -19,15 +19,37 @@ pragma solidity 0.8.15; -import { IWETH } from "src/universal/interfaces/IWETH.sol"; - /// @title WETH98 /// @notice WETH98 is a version of WETH9 upgraded for Solidity 0.8.x. -contract WETH98 is IWETH { +contract WETH98 { + /// @notice Returns the number of decimals the token uses. + /// @return The number of decimals the token uses. uint8 public constant decimals = 18; - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; + mapping(address => uint256) internal _balanceOf; + mapping(address => mapping(address => uint256)) internal _allowance; + + /// @notice Emitted when an approval is made. + /// @param src The address that approved the transfer. + /// @param guy The address that was approved to transfer. + /// @param wad The amount that was approved to transfer. + event Approval(address indexed src, address indexed guy, uint256 wad); + + /// @notice Emitted when a transfer is made. + /// @param src The address that transferred the WETH. + /// @param dst The address that received the WETH. + /// @param wad The amount of WETH that was transferred. + event Transfer(address indexed src, address indexed dst, uint256 wad); + + /// @notice Emitted when a deposit is made. + /// @param dst The address that deposited the WETH. + /// @param wad The amount of WETH that was deposited. + event Deposit(address indexed dst, uint256 wad); + + /// @notice Emitted when a withdrawal is made. + /// @param src The address that withdrew the WETH. + /// @param wad The amount of WETH that was withdrawn. + event Withdrawal(address indexed src, uint256 wad); /// @notice Pipes to deposit. receive() external payable { @@ -39,58 +61,88 @@ contract WETH98 is IWETH { deposit(); } - /// @inheritdoc IWETH - function name() external view virtual override returns (string memory) { + /// @notice Returns the name of the token. + /// @return name_ The name of the token. + function name() external view virtual returns (string memory) { return "Wrapped Ether"; } - /// @inheritdoc IWETH - function symbol() external view virtual override returns (string memory) { + /// @notice Returns the symbol of the token. + /// @return symbol_ The symbol of the token. + function symbol() external view virtual returns (string memory) { return "WETH"; } - /// @inheritdoc IWETH + /// @notice Returns the amount of WETH that the spender can transfer on behalf of the owner. + /// @param owner The address that owns the WETH. + /// @param spender The address that is approved to transfer the WETH. + /// @return The amount of WETH that the spender can transfer on behalf of the owner. + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowance[owner][spender]; + } + + /// @notice Returns the balance of the given address. + /// @param src The address to query the balance of. + /// @return The balance of the given address. + function balanceOf(address src) public view returns (uint256) { + return _balanceOf[src]; + } + + /// @notice Allows WETH to be deposited by sending ether to the contract. function deposit() public payable virtual { - balanceOf[msg.sender] += msg.value; + _balanceOf[msg.sender] += msg.value; emit Deposit(msg.sender, msg.value); } - /// @inheritdoc IWETH + /// @notice Withdraws an amount of ETH. + /// @param wad The amount of ETH to withdraw. function withdraw(uint256 wad) public virtual { - require(balanceOf[msg.sender] >= wad); - balanceOf[msg.sender] -= wad; + require(_balanceOf[msg.sender] >= wad); + _balanceOf[msg.sender] -= wad; payable(msg.sender).transfer(wad); emit Withdrawal(msg.sender, wad); } - /// @inheritdoc IWETH + /// @notice Returns the total supply of WETH. + /// @return The total supply of WETH. function totalSupply() external view returns (uint256) { return address(this).balance; } - /// @inheritdoc IWETH + /// @notice Approves the given address to transfer the WETH on behalf of the caller. + /// @param guy The address that is approved to transfer the WETH. + /// @param wad The amount that is approved to transfer. + /// @return True if the approval was successful. function approve(address guy, uint256 wad) external returns (bool) { - allowance[msg.sender][guy] = wad; + _allowance[msg.sender][guy] = wad; emit Approval(msg.sender, guy, wad); return true; } - /// @inheritdoc IWETH + /// @notice Transfers the given amount of WETH to the given address. + /// @param dst The address to transfer the WETH to. + /// @param wad The amount of WETH to transfer. + /// @return True if the transfer was successful. function transfer(address dst, uint256 wad) external returns (bool) { return transferFrom(msg.sender, dst, wad); } - /// @inheritdoc IWETH + /// @notice Transfers the given amount of WETH from the given address to the given address. + /// @param src The address to transfer the WETH from. + /// @param dst The address to transfer the WETH to. + /// @param wad The amount of WETH to transfer. + /// @return True if the transfer was successful. function transferFrom(address src, address dst, uint256 wad) public returns (bool) { - require(balanceOf[src] >= wad); + require(_balanceOf[src] >= wad); - if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { - require(allowance[src][msg.sender] >= wad); - allowance[src][msg.sender] -= wad; + uint256 senderAllowance = allowance(src, msg.sender); + if (src != msg.sender && senderAllowance != type(uint256).max) { + require(senderAllowance >= wad); + _allowance[src][msg.sender] -= wad; } - balanceOf[src] -= wad; - balanceOf[dst] += wad; + _balanceOf[src] -= wad; + _balanceOf[dst] += wad; emit Transfer(src, dst, wad); diff --git a/packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC721.sol b/packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC721.sol deleted file mode 100644 index 0f9133d7dc676..0000000000000 --- a/packages/contracts-bedrock/src/universal/interfaces/IOptimismMintableERC721.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; - -/// @title IOptimismMintableERC721 -/// @notice Interface for contracts that are compatible with the OptimismMintableERC721 standard. -/// Tokens that follow this standard can be easily transferred across the ERC721 bridge. -interface IOptimismMintableERC721 is IERC721Enumerable { - /// @notice Emitted when a token is minted. - /// @param account Address of the account the token was minted to. - /// @param tokenId Token ID of the minted token. - event Mint(address indexed account, uint256 tokenId); - - /// @notice Emitted when a token is burned. - /// @param account Address of the account the token was burned from. - /// @param tokenId Token ID of the burned token. - event Burn(address indexed account, uint256 tokenId); - - /// @notice Mints some token ID for a user, checking first that contract recipients - /// are aware of the ERC721 protocol to prevent tokens from being forever locked. - /// @param _to Address of the user to mint the token for. - /// @param _tokenId Token ID to mint. - function safeMint(address _to, uint256 _tokenId) external; - - /// @notice Burns a token ID from a user. - /// @param _from Address of the user to burn the token from. - /// @param _tokenId Token ID to burn. - function burn(address _from, uint256 _tokenId) external; - - /// @notice Chain ID of the chain where the remote token is deployed. - function REMOTE_CHAIN_ID() external view returns (uint256); - - /// @notice Address of the token on the remote domain. - function REMOTE_TOKEN() external view returns (address); - - /// @notice Address of the ERC721 bridge on this network. - function BRIDGE() external view returns (address); - - /// @notice Chain ID of the chain where the remote token is deployed. - function remoteChainId() external view returns (uint256); - - /// @notice Address of the token on the remote domain. - function remoteToken() external view returns (address); - - /// @notice Address of the ERC721 bridge on this network. - function bridge() external view returns (address); -} diff --git a/packages/contracts-bedrock/src/universal/interfaces/IWETH.sol b/packages/contracts-bedrock/src/universal/interfaces/IWETH.sol deleted file mode 100644 index 73ef0e9545776..0000000000000 --- a/packages/contracts-bedrock/src/universal/interfaces/IWETH.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title IWETH -/// @notice Interface for WETH9. -interface IWETH { - /// @notice Emitted when an approval is made. - /// @param src The address that approved the transfer. - /// @param guy The address that was approved to transfer. - /// @param wad The amount that was approved to transfer. - event Approval(address indexed src, address indexed guy, uint256 wad); - - /// @notice Emitted when a transfer is made. - /// @param src The address that transferred the WETH. - /// @param dst The address that received the WETH. - /// @param wad The amount of WETH that was transferred. - event Transfer(address indexed src, address indexed dst, uint256 wad); - - /// @notice Emitted when a deposit is made. - /// @param dst The address that deposited the WETH. - /// @param wad The amount of WETH that was deposited. - event Deposit(address indexed dst, uint256 wad); - - /// @notice Emitted when a withdrawal is made. - /// @param src The address that withdrew the WETH. - /// @param wad The amount of WETH that was withdrawn. - event Withdrawal(address indexed src, uint256 wad); - - /// @notice Returns the name of the token. - /// @return The name of the token. - function name() external view returns (string memory); - - /// @notice Returns the symbol of the token. - /// @return The symbol of the token. - function symbol() external view returns (string memory); - - /// @notice Returns the number of decimals the token uses. - /// @return The number of decimals the token uses. - function decimals() external pure returns (uint8); - - /// @notice Returns the balance of the given address. - /// @param owner The address to query the balance of. - /// @return The balance of the given address. - function balanceOf(address owner) external view returns (uint256); - - /// @notice Returns the amount of WETH that the spender can transfer on behalf of the owner. - /// @param owner The address that owns the WETH. - /// @param spender The address that is approved to transfer the WETH. - /// @return The amount of WETH that the spender can transfer on behalf of the owner. - function allowance(address owner, address spender) external view returns (uint256); - - /// @notice Allows WETH to be deposited by sending ether to the contract. - function deposit() external payable; - - /// @notice Withdraws an amount of ETH. - /// @param wad The amount of ETH to withdraw. - function withdraw(uint256 wad) external; - - /// @notice Returns the total supply of WETH. - /// @return The total supply of WETH. - function totalSupply() external view returns (uint256); - - /// @notice Approves the given address to transfer the WETH on behalf of the caller. - /// @param guy The address that is approved to transfer the WETH. - /// @param wad The amount that is approved to transfer. - /// @return True if the approval was successful. - function approve(address guy, uint256 wad) external returns (bool); - - /// @notice Transfers the given amount of WETH to the given address. - /// @param dst The address to transfer the WETH to. - /// @param wad The amount of WETH to transfer. - /// @return True if the transfer was successful. - function transfer(address dst, uint256 wad) external returns (bool); - - /// @notice Transfers the given amount of WETH from the given address to the given address. - /// @param src The address to transfer the WETH from. - /// @param dst The address to transfer the WETH to. - /// @param wad The amount of WETH to transfer. - /// @return True if the transfer was successful. - function transferFrom(address src, address dst, uint256 wad) external returns (bool); -} diff --git a/packages/contracts-bedrock/src/vendor/asterisc/RISCV.sol b/packages/contracts-bedrock/src/vendor/asterisc/RISCV.sol new file mode 100644 index 0000000000000..4a9304935f5cd --- /dev/null +++ b/packages/contracts-bedrock/src/vendor/asterisc/RISCV.sol @@ -0,0 +1,1651 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; + +/// @title RISCV +/// @notice The RISCV contract emulates a single RISCV hart cycle statelessly, using memory proofs to verify the +/// instruction and optional memory access' inclusion in the memory merkle root provided in the trusted +/// prestate witness. +/// @dev https://github.com/ethereum-optimism/asterisc +contract RISCV is IBigStepper { + /// @notice The preimage oracle contract. + IPreimageOracle public oracle; + + /// @notice The version of the contract. + /// @custom:semver 1.2.0-rc.1 + string public constant version = "1.2.0-rc.1"; + + /// @param _oracle The preimage oracle contract. + constructor(IPreimageOracle _oracle) { + oracle = _oracle; + } + + /// @inheritdoc IBigStepper + function step(bytes calldata _stateData, bytes calldata _proof, bytes32 _localContext) public returns (bytes32) { + assembly { + function revertWithCode(code) { + mstore(0, code) + revert(0, 0x20) + } + + function preimageOraclePos() -> out { + // slot of preimageOraclePos field + out := 0 + } + + // + // Yul64 - functions to do 64 bit math - see yul64.go + // + function u64Mask() -> out { + // max uint64 + out := shr(192, not(0)) // 256-64 = 192 + } + + function u32Mask() -> out { + out := U64(shr(toU256(224), not(0))) // 256-32 = 224 + } + + function toU64(v) -> out { + out := v + } + + function shortToU64(v) -> out { + out := v + } + + function shortToU256(v) -> out { + out := v + } + + function longToU256(v) -> out { + out := v + } + + function u256ToU64(v) -> out { + out := and(v, U256(u64Mask())) + } + + function u64ToU256(v) -> out { + out := v + } + + function mask32Signed64(v) -> out { + out := signExtend64(and64(v, u32Mask()), toU64(31)) + } + + function u64Mod() -> out { + // 1 << 64 + out := shl(toU256(64), toU256(1)) + } + + function u64TopBit() -> out { + // 1 << 63 + out := shl(toU256(63), toU256(1)) + } + + function signExtend64(v, bit) -> out { + switch and(v, shl(bit, 1)) + case 0 { + // fill with zeroes, by masking + out := U64(and(U256(v), shr(sub(toU256(63), bit), U256(u64Mask())))) + } + default { + // fill with ones, by or-ing + out := U64(or(U256(v), shl(bit, shr(bit, U256(u64Mask()))))) + } + } + + function signExtend64To256(v) -> out { + switch and(U256(v), u64TopBit()) + case 0 { out := v } + default { out := or(shl(toU256(64), not(0)), v) } + } + + function add64(x, y) -> out { + out := U64(mod(add(U256(x), U256(y)), u64Mod())) + } + + function sub64(x, y) -> out { + out := U64(mod(sub(U256(x), U256(y)), u64Mod())) + } + + function mul64(x, y) -> out { + out := u256ToU64(mul(U256(x), U256(y))) + } + + function div64(x, y) -> out { + out := u256ToU64(div(U256(x), U256(y))) + } + + function sdiv64(x, y) -> out { + // note: signed overflow semantics are the same between Go and EVM assembly + out := u256ToU64(sdiv(signExtend64To256(x), signExtend64To256(y))) + } + + function mod64(x, y) -> out { + out := U64(mod(U256(x), U256(y))) + } + + function smod64(x, y) -> out { + out := u256ToU64(smod(signExtend64To256(x), signExtend64To256(y))) + } + + function not64(x) -> out { + out := u256ToU64(not(U256(x))) + } + + function lt64(x, y) -> out { + out := U64(lt(U256(x), U256(y))) + } + + function gt64(x, y) -> out { + out := U64(gt(U256(x), U256(y))) + } + + function slt64(x, y) -> out { + out := U64(slt(signExtend64To256(x), signExtend64To256(y))) + } + + function sgt64(x, y) -> out { + out := U64(sgt(signExtend64To256(x), signExtend64To256(y))) + } + + function eq64(x, y) -> out { + out := U64(eq(U256(x), U256(y))) + } + + function iszero64(x) -> out { + out := iszero(U256(x)) + } + + function and64(x, y) -> out { + out := U64(and(U256(x), U256(y))) + } + + function or64(x, y) -> out { + out := U64(or(U256(x), U256(y))) + } + + function xor64(x, y) -> out { + out := U64(xor(U256(x), U256(y))) + } + + function shl64(x, y) -> out { + out := u256ToU64(shl(U256(x), U256(y))) + } + + function shr64(x, y) -> out { + out := U64(shr(U256(x), U256(y))) + } + + function sar64(x, y) -> out { + out := u256ToU64(sar(U256(x), signExtend64To256(y))) + } + + // type casts, no-op in yul + function b32asBEWord(v) -> out { + out := v + } + function beWordAsB32(v) -> out { + out := v + } + function U64(v) -> out { + out := v + } + function U256(v) -> out { + out := v + } + function toU256(v) -> out { + out := v + } + + // + // Bit hacking util + // + function bitlen(x) -> n { + if gt(x, sub(shl(128, 1), 1)) { + x := shr(128, x) + n := add(n, 128) + } + if gt(x, sub(shl(64, 1), 1)) { + x := shr(64, x) + n := add(n, 64) + } + if gt(x, sub(shl(32, 1), 1)) { + x := shr(32, x) + n := add(n, 32) + } + if gt(x, sub(shl(16, 1), 1)) { + x := shr(16, x) + n := add(n, 16) + } + if gt(x, sub(shl(8, 1), 1)) { + x := shr(8, x) + n := add(n, 8) + } + if gt(x, sub(shl(4, 1), 1)) { + x := shr(4, x) + n := add(n, 4) + } + if gt(x, sub(shl(2, 1), 1)) { + x := shr(2, x) + n := add(n, 2) + } + if gt(x, sub(shl(1, 1), 1)) { + x := shr(1, x) + n := add(n, 1) + } + if gt(x, 0) { n := add(n, 1) } + } + + function endianSwap(x) -> out { + for { let i := 0 } lt(i, 32) { i := add(i, 1) } { + out := or(shl(8, out), and(x, 0xff)) + x := shr(8, x) + } + } + + // + // State layout + // + function stateSizeMemRoot() -> out { + out := 32 + } + function stateSizePreimageKey() -> out { + out := 32 + } + function stateSizePreimageOffset() -> out { + out := 8 + } + function stateSizePC() -> out { + out := 8 + } + function stateSizeExitCode() -> out { + out := 1 + } + function stateSizeExited() -> out { + out := 1 + } + function stateSizeStep() -> out { + out := 8 + } + function stateSizeHeap() -> out { + out := 8 + } + function stateSizeLoadReservation() -> out { + out := 8 + } + function stateSizeRegisters() -> out { + out := mul(8, 32) + } + + function stateOffsetMemRoot() -> out { + out := 0 + } + function stateOffsetPreimageKey() -> out { + out := add(stateOffsetMemRoot(), stateSizeMemRoot()) + } + function stateOffsetPreimageOffset() -> out { + out := add(stateOffsetPreimageKey(), stateSizePreimageKey()) + } + function stateOffsetPC() -> out { + out := add(stateOffsetPreimageOffset(), stateSizePreimageOffset()) + } + function stateOffsetExitCode() -> out { + out := add(stateOffsetPC(), stateSizePC()) + } + function stateOffsetExited() -> out { + out := add(stateOffsetExitCode(), stateSizeExitCode()) + } + function stateOffsetStep() -> out { + out := add(stateOffsetExited(), stateSizeExited()) + } + function stateOffsetHeap() -> out { + out := add(stateOffsetStep(), stateSizeStep()) + } + function stateOffsetLoadReservation() -> out { + out := add(stateOffsetHeap(), stateSizeHeap()) + } + function stateOffsetRegisters() -> out { + out := add(stateOffsetLoadReservation(), stateSizeLoadReservation()) + } + function stateSize() -> out { + out := add(stateOffsetRegisters(), stateSizeRegisters()) + } + + // + // Initial EVM memory / calldata checks + // + if iszero(eq(mload(0x40), 0x80)) { + // expected memory check: no allocated memory (start after scratch + free-mem-ptr + zero slot = 0x80) + revert(0, 0) + } + if iszero(eq(_stateData.offset, 132)) { + // 32*4+4 = 132 expected state data offset + revert(0, 0) + } + if iszero(eq(calldataload(sub(_stateData.offset, 32)), stateSize())) { + // user-provided state size must match expected state size + revert(0, 0) + } + function paddedLen(v) -> out { + // padded to multiple of 32 bytes + let padding := mod(sub(32, mod(v, 32)), 32) + out := add(v, padding) + } + if iszero(eq(_proof.offset, add(add(_stateData.offset, paddedLen(stateSize())), 32))) { + // 132+stateSize+padding+32 = expected proof offset + revert(0, 0) + } + function proofContentOffset() -> out { + // since we can't reference proof.offset in functions, blame Yul + // 132+362+(32-362%32)+32=548 + out := 548 + } + if iszero(eq(_proof.offset, proofContentOffset())) { revert(0, 0) } + + // + // State loading + // + function memStateOffset() -> out { + out := 0x80 + } + // copy the state calldata into memory, so we can mutate it + mstore(0x40, add(memStateOffset(), stateSize())) // alloc, update free mem pointer + calldatacopy(memStateOffset(), _stateData.offset, stateSize()) // same format in memory as in calldata + + // + // State access + // + function readState(offset, length) -> out { + out := mload(add(memStateOffset(), offset)) // note: the state variables are all big-endian encoded + out := shr(shl(3, sub(32, length)), out) // shift-right to right-align data and reduce to desired length + } + function writeState(offset, length, data) { + let memOffset := add(memStateOffset(), offset) + // left-aligned mask of length bytes + let mask := shl(shl(3, sub(32, length)), not(0)) + let prev := mload(memOffset) + // align data to left + data := shl(shl(3, sub(32, length)), data) + // mask out data from previous word, and apply new data + let result := or(and(prev, not(mask)), data) + mstore(memOffset, result) + } + + function getMemRoot() -> out { + out := readState(stateOffsetMemRoot(), stateSizeMemRoot()) + } + function setMemRoot(v) { + writeState(stateOffsetMemRoot(), stateSizeMemRoot(), v) + } + + function getPreimageKey() -> out { + out := readState(stateOffsetPreimageKey(), stateSizePreimageKey()) + } + function setPreimageKey(k) { + writeState(stateOffsetPreimageKey(), stateSizePreimageKey(), k) + } + + function getPreimageOffset() -> out { + out := readState(stateOffsetPreimageOffset(), stateSizePreimageOffset()) + } + function setPreimageOffset(v) { + writeState(stateOffsetPreimageOffset(), stateSizePreimageOffset(), v) + } + + function getPC() -> out { + out := readState(stateOffsetPC(), stateSizePC()) + } + function setPC(v) { + writeState(stateOffsetPC(), stateSizePC(), v) + } + + function getExited() -> out { + out := readState(stateOffsetExited(), stateSizeExited()) + } + function setExited() { + writeState(stateOffsetExited(), stateSizeExited(), 1) + } + + function getExitCode() -> out { + out := readState(stateOffsetExitCode(), stateSizeExitCode()) + } + function setExitCode(v) { + writeState(stateOffsetExitCode(), stateSizeExitCode(), v) + } + + function getStep() -> out { + out := readState(stateOffsetStep(), stateSizeStep()) + } + function setStep(v) { + writeState(stateOffsetStep(), stateSizeStep(), v) + } + + function getHeap() -> out { + out := readState(stateOffsetHeap(), stateSizeHeap()) + } + function setHeap(v) { + writeState(stateOffsetHeap(), stateSizeHeap(), v) + } + + function getLoadReservation() -> out { + out := readState(stateOffsetLoadReservation(), stateSizeLoadReservation()) + } + function setLoadReservation(addr) { + writeState(stateOffsetLoadReservation(), stateSizeLoadReservation(), addr) + } + + function getRegister(reg) -> out { + if gt64(reg, toU64(31)) { revertWithCode(0xbad4e9) } // cannot load invalid register + + let offset := add64(toU64(stateOffsetRegisters()), mul64(reg, toU64(8))) + out := readState(offset, 8) + } + function setRegister(reg, v) { + if iszero64(reg) { + // reg 0 must stay 0 + // v is a HINT, but no hints are specified by standard spec, or used by us. + leave + } + if gt64(reg, toU64(31)) { revertWithCode(0xbad4e9) } // unknown register + + let offset := add64(toU64(stateOffsetRegisters()), mul64(reg, toU64(8))) + writeState(offset, 8, v) + } + + // + // State output + // + function vmStatus() -> status { + switch getExited() + case 1 { + switch getExitCode() + case 0 { status := 0 } + // VMStatusValid + case 1 { status := 1 } + // VMStatusInvalid + default { status := 2 } // VMStatusPanic + } + default { status := 3 } // VMStatusUnfinished + } + + function computeStateHash() -> out { + // Log the RISC-V state for debugging + log0(memStateOffset(), stateSize()) + + out := keccak256(memStateOffset(), stateSize()) + out := or(and(not(shl(248, 0xFF)), out), shl(248, vmStatus())) + } + + // + // Parse - functions to parse RISC-V instructions - see parse.go + // + function parseImmTypeI(instr) -> out { + out := signExtend64(shr64(toU64(20), instr), toU64(11)) + } + + function parseImmTypeS(instr) -> out { + out := + signExtend64( + or64(shl64(toU64(5), shr64(toU64(25), instr)), and64(shr64(toU64(7), instr), toU64(0x1F))), + toU64(11) + ) + } + + function parseImmTypeB(instr) -> out { + out := + signExtend64( + or64( + or64( + shl64(toU64(1), and64(shr64(toU64(8), instr), toU64(0xF))), + shl64(toU64(5), and64(shr64(toU64(25), instr), toU64(0x3F))) + ), + or64( + shl64(toU64(11), and64(shr64(toU64(7), instr), toU64(1))), + shl64(toU64(12), shr64(toU64(31), instr)) + ) + ), + toU64(12) + ) + } + + function parseImmTypeU(instr) -> out { + out := signExtend64(shr64(toU64(12), instr), toU64(19)) + } + + function parseImmTypeJ(instr) -> out { + out := + signExtend64( + or64( + or64( + and64(shr64(toU64(21), instr), shortToU64(0x3FF)), // 10 bits for index 0:9 + shl64(toU64(10), and64(shr64(toU64(20), instr), toU64(1))) // 1 bit for index 10 + ), + or64( + shl64(toU64(11), and64(shr64(toU64(12), instr), toU64(0xFF))), // 8 bits for index 11:18 + shl64(toU64(19), shr64(toU64(31), instr)) // 1 bit for index 19 + ) + ), + toU64(19) + ) + } + + function parseOpcode(instr) -> out { + out := and64(instr, toU64(0x7F)) + } + + function parseRd(instr) -> out { + out := and64(shr64(toU64(7), instr), toU64(0x1F)) + } + + function parseFunct3(instr) -> out { + out := and64(shr64(toU64(12), instr), toU64(0x7)) + } + + function parseRs1(instr) -> out { + out := and64(shr64(toU64(15), instr), toU64(0x1F)) + } + + function parseRs2(instr) -> out { + out := and64(shr64(toU64(20), instr), toU64(0x1F)) + } + + function parseFunct7(instr) -> out { + out := shr64(toU64(25), instr) + } + + // + // Memory functions + // + function proofOffset(proofIndex) -> offset { + // proof size: 64-5+1=60 (a 64-bit mem-address branch to 32 byte leaf, incl leaf itself), all 32 bytes + offset := mul64(mul64(toU64(proofIndex), toU64(60)), toU64(32)) + offset := add64(offset, proofContentOffset()) + } + + function hashPair(a, b) -> h { + mstore(0, a) + mstore(0x20, b) + h := keccak256(0, 0x40) + } + + function getMemoryB32(addr, proofIndex) -> out { + if and64(addr, toU64(31)) { + // quick addr alignment check + revertWithCode(0xbad10ad0) // addr not aligned with 32 bytes + } + let offset := proofOffset(proofIndex) + let leaf := calldataload(offset) + offset := add64(offset, toU64(32)) + + let path := shr64(toU64(5), addr) // 32 bytes of memory per leaf + let node := leaf // starting from the leaf node, work back up by combining with siblings, to reconstruct + // the root + for { let i := 0 } lt(i, sub(64, 5)) { i := add(i, 1) } { + let sibling := calldataload(offset) + offset := add64(offset, toU64(32)) + switch and64(shr64(toU64(i), path), toU64(1)) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + let memRoot := getMemRoot() + if iszero(eq(b32asBEWord(node), b32asBEWord(memRoot))) { + // verify the root matches + revertWithCode(0xbadf00d1) // bad memory proof + } + out := leaf + } + + // warning: setMemoryB32 does not verify the proof, + // it assumes the same memory proof has been verified with getMemoryB32 + function setMemoryB32(addr, v, proofIndex) { + if and64(addr, toU64(31)) { revertWithCode(0xbad10ad0) } // addr not aligned with 32 bytes + + let offset := proofOffset(proofIndex) + let leaf := v + offset := add64(offset, toU64(32)) + let path := shr64(toU64(5), addr) // 32 bytes of memory per leaf + let node := leaf // starting from the leaf node, work back up by combining with siblings, to reconstruct + // the root + for { let i := 0 } lt(i, sub(64, 5)) { i := add(i, 1) } { + let sibling := calldataload(offset) + offset := add64(offset, toU64(32)) + + switch and64(shr64(toU64(i), path), toU64(1)) + case 0 { node := hashPair(node, sibling) } + case 1 { node := hashPair(sibling, node) } + } + setMemRoot(node) // store new memRoot + } + + // load unaligned, optionally signed, little-endian, integer of 1 ... 8 bytes from memory + function loadMem(addr, size, signed, proofIndexL, proofIndexR) -> out { + if gt(size, 8) { revertWithCode(0xbad512e0) } // cannot load more than 8 bytes + // load/verify left part + let leftAddr := and64(addr, not64(toU64(31))) + let left := b32asBEWord(getMemoryB32(leftAddr, proofIndexL)) + let alignment := sub64(addr, leftAddr) + + let right := 0 + let rightAddr := and64(add64(addr, sub64(size, toU64(1))), not64(toU64(31))) + let leftShamt := sub64(sub64(toU64(32), alignment), size) + let rightShamt := toU64(0) + if iszero64(eq64(leftAddr, rightAddr)) { + // if unaligned, use second proof for the right part + if eq(proofIndexR, 0xff) { revertWithCode(0xbad22220) } // unexpected need for right-side proof in + // loadMem + // load/verify right part + right := b32asBEWord(getMemoryB32(rightAddr, proofIndexR)) + // left content is aligned to right of 32 bytes + leftShamt := toU64(0) + rightShamt := sub64(sub64(toU64(64), alignment), size) + } + + let addr_ := addr + let size_ := size + // left: prepare for byte-taking by right-aligning + left := shr(u64ToU256(shl64(toU64(3), leftShamt)), left) + // right: right-align for byte-taking by right-aligning + right := shr(u64ToU256(shl64(toU64(3), rightShamt)), right) + // loop: + for { let i := 0 } lt(i, size_) { i := add(i, 1) } { + // translate to reverse byte lookup, since we are reading little-endian memory, and need the highest + // byte first. + // effAddr := (addr + size - 1 - i) &^ 31 + let effAddr := and64(sub64(sub64(add64(addr_, size_), toU64(1)), toU64(i)), not64(toU64(31))) + // take a byte from either left or right, depending on the effective address + let b := toU256(0) + switch eq64(effAddr, leftAddr) + case 1 { + b := and(left, toU256(0xff)) + left := shr(toU256(8), left) + } + case 0 { + b := and(right, toU256(0xff)) + right := shr(toU256(8), right) + } + // append it to the output + out := or64(shl64(toU64(8), out), u256ToU64(b)) + } + + if signed { + let signBitShift := sub64(shl64(toU64(3), size_), toU64(1)) + out := signExtend64(out, signBitShift) + } + } + + // Splits the value into a left and a right part, each with a mask (identify data) and a patch (diff + // content). + function leftAndRight(alignment, size, value) -> leftMask, rightMask, leftPatch, rightPatch { + let start := alignment + let end := add64(alignment, size) + for { let i := 0 } lt(i, 64) { i := add(i, 1) } { + let index := toU64(i) + let leftSide := lt64(index, toU64(32)) + switch leftSide + case 1 { + leftPatch := shl(8, leftPatch) + leftMask := shl(8, leftMask) + } + case 0 { + rightPatch := shl(8, rightPatch) + rightMask := shl(8, rightMask) + } + if and64(eq64(lt64(index, start), toU64(0)), lt64(index, end)) { + // if alignment <= i < alignment+size + let b := and(shr(u64ToU256(shl64(toU64(3), sub64(index, alignment))), value), toU256(0xff)) + switch leftSide + case 1 { + leftPatch := or(leftPatch, b) + leftMask := or(leftMask, toU256(0xff)) + } + case 0 { + rightPatch := or(rightPatch, b) + rightMask := or(rightMask, toU256(0xff)) + } + } + } + } + + function storeMemUnaligned(addr, size, value, proofIndexL, proofIndexR) { + if gt(size, 32) { revertWithCode(0xbad512e1) } // cannot store more than 32 bytes + + let leftAddr := and64(addr, not64(toU64(31))) + let rightAddr := and64(add64(addr, sub64(size, toU64(1))), not64(toU64(31))) + let alignment := sub64(addr, leftAddr) + let leftMask, rightMask, leftPatch, rightPatch := leftAndRight(alignment, size, value) + + // load the left base + let left := b32asBEWord(getMemoryB32(leftAddr, proofIndexL)) + // apply the left patch + left := or(and(left, not(leftMask)), leftPatch) + // write the left + setMemoryB32(leftAddr, beWordAsB32(left), proofIndexL) + + // if aligned: nothing more to do here + if eq64(leftAddr, rightAddr) { leave } + if eq(proofIndexR, 0xff) { revertWithCode(0xbad22221) } // unexpected need for right-side proof in + // storeMem + // load the right base (with updated mem root) + let right := b32asBEWord(getMemoryB32(rightAddr, proofIndexR)) + // apply the right patch + right := or(and(right, not(rightMask)), rightPatch) + // write the right (with updated mem root) + setMemoryB32(rightAddr, beWordAsB32(right), proofIndexR) + } + + function storeMem(addr, size, value, proofIndexL, proofIndexR) { + storeMemUnaligned(addr, size, u64ToU256(value), proofIndexL, proofIndexR) + } + + // + // Preimage oracle interactions + // + function writePreimageKey(addr, count) -> out { + // adjust count down, so we only have to read a single 32 byte leaf of memory + let alignment := and64(addr, toU64(31)) + let maxData := sub64(toU64(32), alignment) + if gt64(count, maxData) { count := maxData } + + let dat := b32asBEWord(getMemoryB32(sub64(addr, alignment), 1)) + // shift out leading bits + dat := shl(u64ToU256(shl64(toU64(3), alignment)), dat) + // shift to right end, remove trailing bits + dat := shr(u64ToU256(shl64(toU64(3), sub64(toU64(32), count))), dat) + + let bits := shl(toU256(3), u64ToU256(count)) + + let preImageKey := getPreimageKey() + + // Append to key content by bit-shifting + let key := b32asBEWord(preImageKey) + key := shl(bits, key) + key := or(key, dat) + + // We reset the pre-image value offset back to 0 (the right part of the merkle pair) + setPreimageKey(beWordAsB32(key)) + setPreimageOffset(toU64(0)) + out := count + } + + function readPreimagePart(key, offset) -> dat, datlen { + let addr := sload(preimageOraclePos()) // calling Oracle.readPreimage(bytes32,uint256) + let memPtr := mload(0x40) // get pointer to free memory for preimage interactions + mstore(memPtr, shl(224, 0xe03110e1)) // (32-4)*8=224: right-pad the function selector, and then store it + // as prefix + mstore(add(memPtr, 0x04), key) + mstore(add(memPtr, 0x24), offset) + let res := call(gas(), addr, 0, memPtr, 0x44, 0x00, 0x40) // output into scratch space + if res { + // 1 on success + dat := mload(0x00) + datlen := mload(0x20) + leave + } + revertWithCode(0xbadf00d0) + } + + // Original implementation is at src/cannon/PreimageKeyLib.sol + // but it cannot be used because this is inside assembly block + function localize(preImageKey, localContext_) -> localizedKey { + // Grab the current free memory pointer to restore later. + let ptr := mload(0x40) + // Store the local data key and caller next to each other in memory for hashing. + mstore(0, preImageKey) + mstore(0x20, caller()) + mstore(0x40, localContext_) + // Localize the key with the above `localize` operation. + localizedKey := or(and(keccak256(0, 0x60), not(shl(248, 0xFF))), shl(248, 1)) + // Restore the free memory pointer. + mstore(0x40, ptr) + } + + function readPreimageValue(addr, count, localContext_) -> out { + let preImageKey := getPreimageKey() + let offset := getPreimageOffset() + // If the preimage key is a local key, localize it in the context of the caller. + let preImageKeyPrefix := shr(248, preImageKey) // 256-8=248 + if eq(preImageKeyPrefix, 1) { preImageKey := localize(preImageKey, localContext_) } + // make call to pre-image oracle contract + let pdatB32, pdatlen := readPreimagePart(preImageKey, offset) + if iszero64(pdatlen) { + // EOF + out := toU64(0) + leave + } + let alignment := and64(addr, toU64(31)) // how many bytes addr is offset from being left-aligned + let maxData := sub64(toU64(32), alignment) // higher alignment leaves less room for data this step + if gt64(count, maxData) { count := maxData } + if gt64(count, pdatlen) { + // cannot read more than pdatlen + count := pdatlen + } + + let addr_ := addr + let count_ := count + let bits := shl64(toU64(3), sub64(toU64(32), count_)) // 32-count, in bits + let mask := not(sub(shl(u64ToU256(bits), toU256(1)), toU256(1))) // left-aligned mask for count bytes + let alignmentBits := u64ToU256(shl64(toU64(3), alignment)) + mask := shr(alignmentBits, mask) // mask of count bytes, shifted by alignment + let pdat := shr(alignmentBits, b32asBEWord(pdatB32)) // pdat, shifted by alignment + + // update pre-image reader with updated offset + let newOffset := add64(offset, count_) + setPreimageOffset(newOffset) + + out := count_ + + let node := getMemoryB32(sub64(addr_, alignment), 1) + let dat := and(b32asBEWord(node), not(mask)) // keep old bytes outside of mask + dat := or(dat, and(pdat, mask)) // fill with bytes from pdat + setMemoryB32(sub64(addr_, alignment), beWordAsB32(dat), 1) + } + + // + // Syscall handling + // + function sysCall(localContext_) { + let a7 := getRegister(toU64(17)) + switch a7 + case 93 { + // exit the calling thread. No multi-thread support yet, so just exit. + let a0 := getRegister(toU64(10)) + setExitCode(and(a0, 0xff)) + setExited() + // program stops here, no need to change registers. + } + case 94 { + // exit-group + let a0 := getRegister(toU64(10)) + setExitCode(and(a0, 0xff)) + setExited() + } + case 214 { + // brk + // Go sys_linux_riscv64 runtime will only ever call brk(NULL), i.e. first argument (register a0) set + // to 0. + + // brk(0) changes nothing about the memory, and returns the current page break + let v := shl64(toU64(30), toU64(1)) // set program break at 1 GiB + setRegister(toU64(10), v) + setRegister(toU64(11), toU64(0)) // no error + } + case 222 { + // mmap + // A0 = addr (hint) + let addr := getRegister(toU64(10)) + // A1 = n (length) + let length := getRegister(toU64(11)) + // A2 = prot (memory protection type, can ignore) + // A3 = flags (shared with other process and or written back to file) + let flags := getRegister(toU64(13)) + // A4 = fd (file descriptor, can ignore because we support anon memory only) + let fd := getRegister(toU64(14)) + // A5 = offset (offset in file, we don't support any non-anon memory, so we can ignore this) + + let errCode := 0 + // ensure MAP_ANONYMOUS is set and fd == -1 + switch or(iszero(and(flags, 0x20)), not(eq(fd, u64Mask()))) + case 1 { + addr := u64Mask() + errCode := toU64(0x4d) + } + default { + switch addr + case 0 { + // No hint, allocate it ourselves, by as much as the requested length. + // Increase the length to align it with desired page size if necessary. + let align := and64(length, shortToU64(4095)) + if align { length := add64(length, sub64(shortToU64(4096), align)) } + let prevHeap := getHeap() + addr := prevHeap + setHeap(add64(prevHeap, length)) // increment heap with length + } + default { + // allow hinted memory address (leave it in A0 as return argument) + } + } + + setRegister(toU64(10), addr) + setRegister(toU64(11), errCode) + } + case 63 { + // read + let fd := getRegister(toU64(10)) // A0 = fd + let addr := getRegister(toU64(11)) // A1 = *buf addr + let count := getRegister(toU64(12)) // A2 = count + let n := 0 + let errCode := 0 + switch fd + case 0 { + // stdin + n := toU64(0) // never read anything from stdin + errCode := toU64(0) + } + case 3 { + // hint-read + // say we read it all, to continue execution after reading the hint-write ack response + n := count + errCode := toU64(0) + } + case 5 { + // preimage read + n := readPreimageValue(addr, count, localContext_) + errCode := toU64(0) + } + default { + n := u64Mask() // -1 (reading error) + errCode := toU64(0x4d) // EBADF + } + setRegister(toU64(10), n) + setRegister(toU64(11), errCode) + } + case 64 { + // write + let fd := getRegister(toU64(10)) // A0 = fd + let addr := getRegister(toU64(11)) // A1 = *buf addr + let count := getRegister(toU64(12)) // A2 = count + let n := 0 + let errCode := 0 + switch fd + case 1 { + // stdout + n := count // write completes fully in single instruction step + errCode := toU64(0) + } + case 2 { + // stderr + n := count // write completes fully in single instruction step + errCode := toU64(0) + } + case 4 { + // hint-write + n := count + errCode := toU64(0) + } + case 6 { + // pre-image key-write + n := writePreimageKey(addr, count) + errCode := toU64(0) // no error + } + default { + // any other file, including (3) hint read (5) preimage read + n := u64Mask() // -1 (writing error) + errCode := toU64(0x4d) // EBADF + } + setRegister(toU64(10), n) + setRegister(toU64(11), errCode) + } + case 25 { + // fcntl - file descriptor manipulation / info lookup + let fd := getRegister(toU64(10)) // A0 = fd + let cmd := getRegister(toU64(11)) // A1 = cmd + let out := 0 + let errCode := 0 + switch cmd + case 0x1 { + // F_GETFD: get file descriptor flags + switch fd + case 0 { + // stdin + out := toU64(0) // no flag set + } + case 1 { + // stdout + out := toU64(0) // no flag set + } + case 2 { + // stderr + out := toU64(0) // no flag set + } + case 3 { + // hint-read + out := toU64(0) // no flag set + } + case 4 { + // hint-write + out := toU64(0) // no flag set + } + case 5 { + // pre-image read + out := toU64(0) // no flag set + } + case 6 { + // pre-image write + out := toU64(0) // no flag set + } + default { + out := u64Mask() + errCode := toU64(0x4d) //EBADF + } + } + case 0x3 { + // F_GETFL: get file descriptor flags + switch fd + case 0 { + // stdin + out := toU64(0) // O_RDONLY + } + case 1 { + // stdout + out := toU64(1) // O_WRONLY + } + case 2 { + // stderr + out := toU64(1) // O_WRONLY + } + case 3 { + // hint-read + out := toU64(0) // O_RDONLY + } + case 4 { + // hint-write + out := toU64(1) // O_WRONLY + } + case 5 { + // pre-image read + out := toU64(0) // O_RDONLY + } + case 6 { + // pre-image write + out := toU64(1) // O_WRONLY + } + default { + out := u64Mask() + errCode := toU64(0x4d) // EBADF + } + } + default { + // no other commands: don't allow changing flags, duplicating FDs, etc. + out := u64Mask() + errCode := toU64(0x16) // EINVAL (cmd not recognized by this kernel) + } + setRegister(toU64(10), out) + setRegister(toU64(11), errCode) // EBADF + } + case 56 { + // openat - the Go linux runtime will try to open optional /sys/kernel files for performance hints + setRegister(toU64(10), u64Mask()) + setRegister(toU64(11), toU64(0xd)) // EACCES - no access allowed + } + case 113 { + // clock_gettime + let addr := getRegister(toU64(11)) // addr of timespec struct + // write 1337s + 42ns as time + let value := or(shortToU256(1337), shl(shortToU256(64), toU256(42))) + storeMemUnaligned(addr, toU64(16), value, 1, 2) + setRegister(toU64(10), toU64(0)) + setRegister(toU64(11), toU64(0)) + } + case 220 { + // clone - not supported + setRegister(toU64(10), toU64(1)) + setRegister(toU64(11), toU64(0)) + } + case 163 { + // getrlimit + let res := getRegister(toU64(10)) + let addr := getRegister(toU64(11)) + switch res + case 0x7 { + // RLIMIT_NOFILE + // first 8 bytes: soft limit. 1024 file handles max open + // second 8 bytes: hard limit + storeMemUnaligned( + addr, toU64(16), or(shortToU256(1024), shl(toU256(64), shortToU256(1024))), 1, 2 + ) + setRegister(toU64(10), toU64(0)) + setRegister(toU64(11), toU64(0)) + } + default { revertWithCode(0xf0012) } // unrecognized resource limit lookup + } + case 261 { + // prlimit64 -- unsupported, we have getrlimit, is prlimit64 even called? + revertWithCode(0xf001ca11) // unsupported system call + } + case 422 { + // futex - not supported, for now + revertWithCode(0xf001ca11) // unsupported system call + } + case 101 { + // nanosleep - not supported, for now + revertWithCode(0xf001ca11) // unsupported system call + } + default { + // Ignore(no-op) unsupported system calls + setRegister(toU64(10), toU64(0)) + setRegister(toU64(11), toU64(0)) + } + } + + // + // Instruction execution + // + if getExited() { + // early exit if we can + mstore(0, computeStateHash()) + return(0, 0x20) + } + setStep(add64(getStep(), toU64(1))) + + let _pc := getPC() + let instr := loadMem(_pc, toU64(4), false, 0, 0xff) // raw instruction + + // these fields are ignored if not applicable to the instruction type / opcode + let opcode := parseOpcode(instr) + let rd := parseRd(instr) // destination register index + let funct3 := parseFunct3(instr) + let rs1 := parseRs1(instr) // source register 1 index + let rs2 := parseRs2(instr) // source register 2 index + let funct7 := parseFunct7(instr) + + switch opcode + case 0x03 { + let pc_ := _pc + // 000_0011: memory loading + // LB, LH, LW, LD, LBU, LHU, LWU + let imm := parseImmTypeI(instr) + let signed := iszero64(and64(funct3, toU64(4))) // 4 = 100 -> bitflag + let size := shl64(and64(funct3, toU64(3)), toU64(1)) // 3 = 11 -> 1, 2, 4, 8 bytes size + let rs1Value := getRegister(rs1) + let memIndex := add64(rs1Value, signExtend64(imm, toU64(11))) + let rdValue := loadMem(memIndex, size, signed, 1, 2) + setRegister(rd, rdValue) + setPC(add64(pc_, toU64(4))) + } + case 0x23 { + let pc_ := _pc + // 010_0011: memory storing + // SB, SH, SW, SD + let imm := parseImmTypeS(instr) + let size := shl64(funct3, toU64(1)) + let value := getRegister(rs2) + let rs1Value := getRegister(rs1) + let memIndex := add64(rs1Value, signExtend64(imm, toU64(11))) + storeMem(memIndex, size, value, 1, 2) + setPC(add64(pc_, toU64(4))) + } + case 0x63 { + // 110_0011: branching + let rs1Value := getRegister(rs1) + let rs2Value := getRegister(rs2) + let branchHit := toU64(0) + switch funct3 + case 0 { + // 000 = BEQ + branchHit := eq64(rs1Value, rs2Value) + } + case 1 { + // 001 = BNE + branchHit := and64(not64(eq64(rs1Value, rs2Value)), toU64(1)) + } + case 4 { + // 100 = BLT + branchHit := slt64(rs1Value, rs2Value) + } + case 5 { + // 101 = BGE + branchHit := and64(not64(slt64(rs1Value, rs2Value)), toU64(1)) + } + case 6 { + // 110 = BLTU + branchHit := lt64(rs1Value, rs2Value) + } + case 7 { + // 111 := BGEU + branchHit := and64(not64(lt64(rs1Value, rs2Value)), toU64(1)) + } + switch branchHit + case 0 { _pc := add64(_pc, toU64(4)) } + default { + let imm := parseImmTypeB(instr) + // imm12 is a signed offset, in multiples of 2 bytes. + // So it's really 13 bits with a hardcoded 0 bit. + _pc := add64(_pc, imm) + } + // not like the other opcodes: nothing to write to rd register, and PC has already changed + setPC(_pc) + } + case 0x13 { + // 001_0011: immediate arithmetic and logic + let rs1Value := getRegister(rs1) + let imm := parseImmTypeI(instr) + let rdValue := 0 + switch funct3 + case 0 { + // 000 = ADDI + rdValue := add64(rs1Value, imm) + } + case 1 { + // 001 = SLLI + rdValue := shl64(and64(imm, toU64(0x3F)), rs1Value) // lower 6 bits in 64 bit mode + } + case 2 { + // 010 = SLTI + rdValue := slt64(rs1Value, imm) + } + case 3 { + // 011 = SLTIU + rdValue := lt64(rs1Value, imm) + } + case 4 { + // 100 = XORI + rdValue := xor64(rs1Value, imm) + } + case 5 { + // 101 = SR~ + switch shr64(toU64(6), imm) + // in rv64i the top 6 bits select the shift type + case 0x00 { + // 000000 = SRLI + rdValue := shr64(and64(imm, toU64(0x3F)), rs1Value) // lower 6 bits in 64 bit mode + } + case 0x10 { + // 010000 = SRAI + rdValue := sar64(and64(imm, toU64(0x3F)), rs1Value) // lower 6 bits in 64 bit mode + } + } + case 6 { + // 110 = ORI + rdValue := or64(rs1Value, imm) + } + case 7 { + // 111 = ANDI + rdValue := and64(rs1Value, imm) + } + setRegister(rd, rdValue) + setPC(add64(_pc, toU64(4))) + } + case 0x1B { + // 001_1011: immediate arithmetic and logic signed 32 bit + let rs1Value := getRegister(rs1) + let imm := parseImmTypeI(instr) + let rdValue := 0 + switch funct3 + case 0 { + // 000 = ADDIW + rdValue := mask32Signed64(add64(rs1Value, imm)) + } + case 1 { + // 001 = SLLIW + rdValue := mask32Signed64(shl64(and64(imm, toU64(0x1F)), rs1Value)) + } + case 5 { + // 101 = SR~ + let shamt := and64(imm, toU64(0x1F)) + switch shr64(toU64(5), imm) + // top 7 bits select the shift type + case 0x00 { + // 0000000 = SRLIW + rdValue := signExtend64(shr64(shamt, and64(rs1Value, u32Mask())), toU64(31)) + } + case 0x20 { + // 0100000 = SRAIW + rdValue := signExtend64(shr64(shamt, and64(rs1Value, u32Mask())), sub64(toU64(31), shamt)) + } + } + setRegister(rd, rdValue) + setPC(add64(_pc, toU64(4))) + } + case 0x33 { + // 011_0011: register arithmetic and logic + let rs1Value := getRegister(rs1) + let rs2Value := getRegister(rs2) + let rdValue := 0 + switch funct7 + case 1 { + // RV M extension + switch funct3 + case 0 { + // 000 = MUL: signed x signed + rdValue := mul64(rs1Value, rs2Value) + } + case 1 { + // 001 = MULH: upper bits of signed x signed + rdValue := + u256ToU64(shr(toU256(64), mul(signExtend64To256(rs1Value), signExtend64To256(rs2Value)))) + } + case 2 { + // 010 = MULHSU: upper bits of signed x unsigned + rdValue := u256ToU64(shr(toU256(64), mul(signExtend64To256(rs1Value), u64ToU256(rs2Value)))) + } + case 3 { + // 011 = MULHU: upper bits of unsigned x unsigned + rdValue := u256ToU64(shr(toU256(64), mul(u64ToU256(rs1Value), u64ToU256(rs2Value)))) + } + case 4 { + // 100 = DIV + switch rs2Value + case 0 { rdValue := u64Mask() } + default { rdValue := sdiv64(rs1Value, rs2Value) } + } + case 5 { + // 101 = DIVU + switch rs2Value + case 0 { rdValue := u64Mask() } + default { rdValue := div64(rs1Value, rs2Value) } + } + case 6 { + // 110 = REM + switch rs2Value + case 0 { rdValue := rs1Value } + default { rdValue := smod64(rs1Value, rs2Value) } + } + case 7 { + // 111 = REMU + switch rs2Value + case 0 { rdValue := rs1Value } + default { rdValue := mod64(rs1Value, rs2Value) } + } + } + default { + switch funct3 + case 0 { + // 000 = ADD/SUB + switch funct7 + case 0x00 { + // 0000000 = ADD + rdValue := add64(rs1Value, rs2Value) + } + case 0x20 { + // 0100000 = SUB + rdValue := sub64(rs1Value, rs2Value) + } + } + case 1 { + // 001 = SLL + rdValue := shl64(and64(rs2Value, toU64(0x3F)), rs1Value) // only the low 6 bits are consider in + // RV6VI + } + case 2 { + // 010 = SLT + rdValue := slt64(rs1Value, rs2Value) + } + case 3 { + // 011 = SLTU + rdValue := lt64(rs1Value, rs2Value) + } + case 4 { + // 100 = XOR + rdValue := xor64(rs1Value, rs2Value) + } + case 5 { + // 101 = SR~ + switch funct7 + case 0x00 { + // 0000000 = SRL + rdValue := shr64(and64(rs2Value, toU64(0x3F)), rs1Value) // logical: fill with zeroes + } + case 0x20 { + // 0100000 = SRA + rdValue := sar64(and64(rs2Value, toU64(0x3F)), rs1Value) // arithmetic: sign bit is extended + } + } + case 6 { + // 110 = OR + rdValue := or64(rs1Value, rs2Value) + } + case 7 { + // 111 = AND + rdValue := and64(rs1Value, rs2Value) + } + } + setRegister(rd, rdValue) + setPC(add64(_pc, toU64(4))) + } + case 0x3B { + // 011_1011: register arithmetic and logic in 32 bits + let rs1Value := getRegister(rs1) + let rs2Value := getRegister(rs2) + let rdValue := 0 + switch funct7 + case 1 { + // RV M extension + switch funct3 + case 0 { + // 000 = MULW + rdValue := mask32Signed64(mul64(and64(rs1Value, u32Mask()), and64(rs2Value, u32Mask()))) + } + case 4 { + // 100 = DIVW + switch rs2Value + case 0 { rdValue := u64Mask() } + default { + rdValue := mask32Signed64(sdiv64(mask32Signed64(rs1Value), mask32Signed64(rs2Value))) + } + } + case 5 { + // 101 = DIVUW + switch rs2Value + case 0 { rdValue := u64Mask() } + default { + rdValue := mask32Signed64(div64(and64(rs1Value, u32Mask()), and64(rs2Value, u32Mask()))) + } + } + case 6 { + // 110 = REMW + switch rs2Value + case 0 { rdValue := mask32Signed64(rs1Value) } + default { + rdValue := mask32Signed64(smod64(mask32Signed64(rs1Value), mask32Signed64(rs2Value))) + } + } + case 7 { + // 111 = REMUW + switch rs2Value + case 0 { rdValue := mask32Signed64(rs1Value) } + default { + rdValue := mask32Signed64(mod64(and64(rs1Value, u32Mask()), and64(rs2Value, u32Mask()))) + } + } + } + default { + switch funct3 + case 0 { + // 000 = ADDW/SUBW + switch funct7 + case 0x00 { + // 0000000 = ADDW + rdValue := mask32Signed64(add64(and64(rs1Value, u32Mask()), and64(rs2Value, u32Mask()))) + } + case 0x20 { + // 0100000 = SUBW + rdValue := mask32Signed64(sub64(and64(rs1Value, u32Mask()), and64(rs2Value, u32Mask()))) + } + } + case 1 { + // 001 = SLLW + rdValue := mask32Signed64(shl64(and64(rs2Value, toU64(0x1F)), rs1Value)) + } + case 5 { + // 101 = SR~ + let shamt := and64(rs2Value, toU64(0x1F)) + switch funct7 + case 0x00 { + // 0000000 = SRLW + rdValue := signExtend64(shr64(shamt, and64(rs1Value, u32Mask())), toU64(31)) + } + case 0x20 { + // 0100000 = SRAW + rdValue := signExtend64(shr64(shamt, and64(rs1Value, u32Mask())), sub64(toU64(31), shamt)) + } + } + } + setRegister(rd, rdValue) + setPC(add64(_pc, toU64(4))) + } + case 0x37 { + // 011_0111: LUI = Load upper immediate + let imm := parseImmTypeU(instr) + let rdValue := shl64(toU64(12), imm) + setRegister(rd, rdValue) + setPC(add64(_pc, toU64(4))) + } + case 0x17 { + // 001_0111: AUIPC = Add upper immediate to PC + let imm := parseImmTypeU(instr) + let rdValue := add64(_pc, signExtend64(shl64(toU64(12), imm), toU64(31))) + setRegister(rd, rdValue) + setPC(add64(_pc, toU64(4))) + } + case 0x6F { + // 110_1111: JAL = Jump and link + let imm := parseImmTypeJ(instr) + let rdValue := add64(_pc, toU64(4)) + setRegister(rd, rdValue) + setPC(add64(_pc, signExtend64(shl64(toU64(1), imm), toU64(20)))) // signed offset in multiples of 2 + // bytes (last bit is there, but ignored) + } + case 0x67 { + // 110_0111: JALR = Jump and link register + let rs1Value := getRegister(rs1) + let imm := parseImmTypeI(instr) + let rdValue := add64(_pc, toU64(4)) + setRegister(rd, rdValue) + setPC(and64(add64(rs1Value, signExtend64(imm, toU64(11))), xor64(u64Mask(), toU64(1)))) // least + // significant bit is set to 0 + } + case 0x73 { + // 111_0011: environment things + switch funct3 + case 0 { + // 000 = ECALL/EBREAK + switch shr64(toU64(20), instr) + // I-type, top 12 bits + case 0 { + // imm12 = 000000000000 ECALL + sysCall(_localContext) + setPC(add64(_pc, toU64(4))) + } + default { + // imm12 = 000000000001 EBREAK + setPC(add64(_pc, toU64(4))) // ignore breakpoint + } + } + default { + // CSR instructions + setRegister(rd, toU64(0)) // ignore CSR instructions + setPC(add64(_pc, toU64(4))) + } + } + case 0x2F { + // 010_1111: RV{32,64}A and RV{32,64}A atomic operations extension + // acquire and release bits: + // aq := and64(shr64(toU64(1), funct7), toU64(1)) + // rl := and64(funct7, toU64(1)) + // if none set: unordered + // if aq is set: no following mem ops observed before acquire mem op + // if rl is set: release mem op not observed before earlier mem ops + // if both set: sequentially consistent + // These are no-op here because there is no pipeline of mem ops to acquire/release. + + // 0b010 == RV32A W variants + // 0b011 == RV64A D variants + let size := shl64(funct3, toU64(1)) + if or(lt64(size, toU64(4)), gt64(size, toU64(8))) { revertWithCode(0xbada70) } // bad AMO size + + let addr := getRegister(rs1) + if and64(addr, toU64(3)) { + // quick addr alignment check + revertWithCode(0xbad10ad0) // addr not aligned with 4 bytes + } + + let op := shr64(toU64(2), funct7) + switch op + case 0x2 { + // 00010 = LR = Load Reserved + let v := loadMem(addr, size, true, 1, 2) + setRegister(rd, v) + setLoadReservation(addr) + } + case 0x3 { + // 00011 = SC = Store Conditional + let rdValue := toU64(1) + if eq64(addr, getLoadReservation()) { + let rs2Value := getRegister(rs2) + storeMem(addr, size, rs2Value, 1, 2) + rdValue := toU64(0) + } + setRegister(rd, rdValue) + setLoadReservation(toU64(0)) + } + default { + // AMO: Atomic Memory Operation + let rs2Value := getRegister(rs2) + if eq64(size, toU64(4)) { rs2Value := mask32Signed64(rs2Value) } + let value := rs2Value + let v := loadMem(addr, size, true, 1, 2) + let rdValue := v + switch op + case 0x0 { + // 00000 = AMOADD = add + v := add64(v, value) + } + case 0x1 { + // 00001 = AMOSWAP + v := value + } + case 0x4 { + // 00100 = AMOXOR = xor + v := xor64(v, value) + } + case 0x8 { + // 01000 = AMOOR = or + v := or64(v, value) + } + case 0xc { + // 01100 = AMOAND = and + v := and64(v, value) + } + case 0x10 { + // 10000 = AMOMIN = min signed + if slt64(value, v) { v := value } + } + case 0x14 { + // 10100 = AMOMAX = max signed + if sgt64(value, v) { v := value } + } + case 0x18 { + // 11000 = AMOMINU = min unsigned + if lt64(value, v) { v := value } + } + case 0x1c { + // 11100 = AMOMAXU = max unsigned + if gt64(value, v) { v := value } + } + default { revertWithCode(0xf001a70) } // unknown atomic operation + + storeMem(addr, size, v, 1, 3) // after overwriting 1, proof 2 is no longer valid + setRegister(rd, rdValue) + } + setPC(add64(_pc, toU64(4))) + } + case 0x0F { + // 000_1111: fence + // Used to impose additional ordering constraints; flushing the mem operation pipeline. + // This VM doesn't have a pipeline, nor additional harts, so this is a no-op. + // FENCE / FENCE.TSO / FENCE.I all no-op: there's nothing to synchronize. + setPC(add64(_pc, toU64(4))) + } + case 0x07 { + // FLW/FLD: floating point load word/double + setPC(add64(_pc, toU64(4))) // no-op this. + } + case 0x27 { + // FSW/FSD: floating point store word/double + setPC(add64(_pc, toU64(4))) // no-op this. + } + case 0x53 { + // FADD etc. no-op is enough to pass Go runtime check + setPC(add64(_pc, toU64(4))) // no-op this. + } + default { revertWithCode(0xf001c0de) } // unknown instruction opcode + + mstore(0, computeStateHash()) + return(0, 0x20) + } + } +} diff --git a/packages/contracts-bedrock/src/vendor/eas/EAS.sol b/packages/contracts-bedrock/src/vendor/eas/EAS.sol index 1cebdc819343c..dd8840789d504 100644 --- a/packages/contracts-bedrock/src/vendor/eas/EAS.sol +++ b/packages/contracts-bedrock/src/vendor/eas/EAS.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { EIP1271Verifier } from "src/vendor/eas/eip1271/EIP1271Verifier.sol"; import { ISchemaResolver } from "src/vendor/eas/resolver/ISchemaResolver.sol"; @@ -80,8 +80,8 @@ contract EAS is IEAS, ISemver, EIP1271Verifier { uint256[MAX_GAP - 3] private __gap; /// @notice Semantic version. - /// @custom:semver 1.4.1-beta.1 - string public constant version = "1.4.1-beta.1"; + /// @custom:semver 1.4.1-beta.2 + string public constant version = "1.4.1-beta.2"; /// @dev Creates a new EAS instance. constructor() EIP1271Verifier("EAS", "1.3.0") { } diff --git a/packages/contracts-bedrock/src/vendor/eas/SchemaRegistry.sol b/packages/contracts-bedrock/src/vendor/eas/SchemaRegistry.sol index 1adca3d6c3e93..98f87c35b53c2 100644 --- a/packages/contracts-bedrock/src/vendor/eas/SchemaRegistry.sol +++ b/packages/contracts-bedrock/src/vendor/eas/SchemaRegistry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; import { ISchemaResolver } from "src/vendor/eas/resolver/ISchemaResolver.sol"; import { EMPTY_UID, MAX_GAP } from "src/vendor/eas/Common.sol"; import { ISchemaRegistry, SchemaRecord } from "src/vendor/eas/ISchemaRegistry.sol"; @@ -20,8 +20,8 @@ contract SchemaRegistry is ISchemaRegistry, ISemver { uint256[MAX_GAP - 1] private __gap; /// @notice Semantic version. - /// @custom:semver 1.3.1-beta.1 - string public constant version = "1.3.1-beta.1"; + /// @custom:semver 1.3.1-beta.2 + string public constant version = "1.3.1-beta.2"; /// @inheritdoc ISchemaRegistry function register(string calldata schema, ISchemaResolver resolver, bool revocable) external returns (bytes32) { diff --git a/packages/contracts-bedrock/src/vendor/eas/resolver/ISchemaResolver.sol b/packages/contracts-bedrock/src/vendor/eas/resolver/ISchemaResolver.sol index 51aac1ece23ba..d2089f83af86a 100644 --- a/packages/contracts-bedrock/src/vendor/eas/resolver/ISchemaResolver.sol +++ b/packages/contracts-bedrock/src/vendor/eas/resolver/ISchemaResolver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { Attestation } from "../Common.sol"; +import { Attestation } from "src/vendor/eas/Common.sol"; /// @title ISchemaResolver /// @notice The interface of an optional schema resolver. diff --git a/packages/contracts-bedrock/test/L1/DataAvailabilityChallenge.t.sol b/packages/contracts-bedrock/test/L1/DataAvailabilityChallenge.t.sol index dd14b349c68ab..ab0ca82c61fd8 100644 --- a/packages/contracts-bedrock/test/L1/DataAvailabilityChallenge.t.sol +++ b/packages/contracts-bedrock/test/L1/DataAvailabilityChallenge.t.sol @@ -6,7 +6,7 @@ import { ChallengeStatus, Challenge, CommitmentType -} from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; +} from "interfaces/L1/IDataAvailabilityChallenge.sol"; import { computeCommitmentKeccak256 } from "src/L1/DataAvailabilityChallenge.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol"; @@ -17,20 +17,20 @@ contract DataAvailabilityChallengeTest is CommonTest { super.setUp(); } - function testDeposit() public { + function test_deposit_succeeds() public { assertEq(dataAvailabilityChallenge.balances(address(this)), 0); dataAvailabilityChallenge.deposit{ value: 1000 }(); assertEq(dataAvailabilityChallenge.balances(address(this)), 1000); } - function testReceive() public { + function test_receive_succeeds() public { assertEq(dataAvailabilityChallenge.balances(address(this)), 0); (bool success,) = payable(address(dataAvailabilityChallenge)).call{ value: 1000 }(""); assertTrue(success); assertEq(dataAvailabilityChallenge.balances(address(this)), 1000); } - function testWithdraw(address sender, uint256 amount) public { + function test_withdraw_succeeds(address sender, uint256 amount) public { assumePayable(sender); assumeNotPrecompile(sender); // EntryPoint will revert if using amount > type(uint112).max. @@ -52,7 +52,34 @@ contract DataAvailabilityChallengeTest is CommonTest { assertEq(sender.balance, amount); } - function testChallengeSuccess(address challenger, uint256 challengedBlockNumber, bytes calldata preImage) public { + function test_withdraw_fails_reverts(address sender, uint256 amount) public { + assumePayable(sender); + assumeNotPrecompile(sender); + // EntryPoint will revert if using amount > type(uint112).max. + vm.assume(sender != Preinstalls.EntryPoint_v060); + vm.assume(sender != address(dataAvailabilityChallenge)); + vm.assume(sender != deploy.mustGetAddress("DataAvailabilityChallenge")); + vm.assume(sender.balance == 0); + vm.deal(sender, amount); + + vm.prank(sender); + dataAvailabilityChallenge.deposit{ value: amount }(); + + assertEq(dataAvailabilityChallenge.balances(sender), amount); + assertEq(sender.balance, 0); + + vm.etch(sender, hex"fe"); + vm.expectRevert(abi.encodeWithSelector(IDataAvailabilityChallenge.WithdrawalFailed.selector)); + dataAvailabilityChallenge.withdraw(); + } + + function test_challenge_succeeds( + address challenger, + uint256 challengedBlockNumber, + bytes calldata preImage + ) + public + { bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); // Assume the challenger is not the 0 address @@ -99,7 +126,13 @@ contract DataAvailabilityChallengeTest is CommonTest { assertEq(dataAvailabilityChallenge.balances(challenger), 0); } - function testChallengeDeposit(address challenger, uint256 challengedBlockNumber, bytes memory preImage) public { + function test_challenge_deposit_succeeds( + address challenger, + uint256 challengedBlockNumber, + bytes memory preImage + ) + public + { bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); // Assume the challenger is not the 0 address @@ -142,7 +175,7 @@ contract DataAvailabilityChallengeTest is CommonTest { assertEq(dataAvailabilityChallenge.balances(challenger), 0); } - function testChallengeFailBondTooLow() public { + function test_challenge_bondTooLow_reverts() public { uint256 requiredBond = dataAvailabilityChallenge.bondSize(); uint256 actualBond = requiredBond - 1; dataAvailabilityChallenge.deposit{ value: actualBond }(); @@ -153,7 +186,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.challenge(0, computeCommitmentKeccak256("some hash")); } - function testChallengeFailChallengeExists() public { + function test_challenge_challengeExists_reverts() public { // Move to a block after the hash to challenge vm.roll(2); @@ -176,7 +209,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.challenge(0, computeCommitmentKeccak256("some other hash")); } - function testChallengeFailBeforeChallengeWindow() public { + function test_challenge_beforeChallengeWindow_reverts() public { uint256 challengedBlockNumber = 1; bytes memory challengedCommitment = computeCommitmentKeccak256("some hash"); @@ -189,7 +222,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.challenge(challengedBlockNumber, challengedCommitment); } - function testChallengeFailAfterChallengeWindow() public { + function test_challenge_afterChallengeWindow_reverts() public { uint256 challengedBlockNumber = 1; bytes memory challengedCommitment = computeCommitmentKeccak256("some hash"); @@ -202,12 +235,13 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.challenge(challengedBlockNumber, challengedCommitment); } - function testResolveSuccess( + function test_resolve_succeeds( address challenger, address resolver, bytes memory preImage, uint256 challengedBlockNumber, uint256 resolverRefundPercentage, + uint64 bondSize, uint128 txGasPrice ) public @@ -217,6 +251,9 @@ contract DataAvailabilityChallengeTest is CommonTest { vm.assume(resolver != address(0)); vm.assume(challenger != resolver); + vm.prank(dataAvailabilityChallenge.owner()); + dataAvailabilityChallenge.setBondSize(bondSize); + // Bound the resolver refund percentage to 100 resolverRefundPercentage = bound(resolverRefundPercentage, 0, 100); @@ -239,7 +276,6 @@ contract DataAvailabilityChallengeTest is CommonTest { vm.roll(challengedBlockNumber + 1); // Challenge the hash - uint256 bondSize = dataAvailabilityChallenge.bondSize(); vm.deal(challenger, bondSize); vm.prank(challenger); dataAvailabilityChallenge.challenge{ value: bondSize }(challengedBlockNumber, challengedCommitment); @@ -247,6 +283,26 @@ contract DataAvailabilityChallengeTest is CommonTest { // Store the address(0) balance before resolving to assert the burned amount later uint256 zeroAddressBalanceBeforeResolve = address(0).balance; + // Assert challenger balance after bond distribution + uint256 resolutionCost = ( + dataAvailabilityChallenge.fixedResolutionCost() + + preImage.length * dataAvailabilityChallenge.variableResolutionCost() + / dataAvailabilityChallenge.variableResolutionCostPrecision() + ) * block.basefee; + uint256 challengerRefund = bondSize > resolutionCost ? bondSize - resolutionCost : 0; + uint256 resolverRefund = resolutionCost * dataAvailabilityChallenge.resolverRefundPercentage() / 100; + resolverRefund = resolverRefund > resolutionCost ? resolutionCost : resolverRefund; + resolverRefund = resolverRefund > bondSize ? bondSize : resolverRefund; + + if (challengerRefund > 0) { + vm.expectEmit(true, true, true, true); + emit BalanceChanged(challenger, challengerRefund); + } + if (resolverRefund > 0) { + vm.expectEmit(true, true, true, true); + emit BalanceChanged(resolver, resolverRefund); + } + // Resolve the challenge vm.prank(resolver); dataAvailabilityChallenge.resolve(challengedBlockNumber, challengedCommitment, preImage); @@ -262,28 +318,73 @@ contract DataAvailabilityChallengeTest is CommonTest { uint8(dataAvailabilityChallenge.getChallengeStatus(challengedBlockNumber, challengedCommitment)), uint8(ChallengeStatus.Resolved) ); - - // Assert challenger balance after bond distribution - uint256 resolutionCost = ( - dataAvailabilityChallenge.fixedResolutionCost() - + preImage.length * dataAvailabilityChallenge.variableResolutionCost() - / dataAvailabilityChallenge.variableResolutionCostPrecision() - ) * block.basefee; - uint256 challengerRefund = bondSize > resolutionCost ? bondSize - resolutionCost : 0; - assertEq(dataAvailabilityChallenge.balances(challenger), challengerRefund, "challenger refund"); - - // Assert resolver balance after bond distribution - uint256 resolverRefund = resolutionCost * dataAvailabilityChallenge.resolverRefundPercentage() / 100; - resolverRefund = resolverRefund > resolutionCost ? resolutionCost : resolverRefund; - resolverRefund = resolverRefund > bondSize ? bondSize : resolverRefund; - assertEq(dataAvailabilityChallenge.balances(resolver), resolverRefund, "resolver refund"); + address _challenger = challenger; + address _resolver = resolver; + assertEq(dataAvailabilityChallenge.balances(_challenger), challengerRefund, "challenger refund"); + assertEq(dataAvailabilityChallenge.balances(_resolver), resolverRefund, "resolver refund"); // Assert burned amount after bond distribution uint256 burned = bondSize - challengerRefund - resolverRefund; assertEq(address(0).balance - zeroAddressBalanceBeforeResolve, burned, "burned bond"); } - function testResolveFailNonExistentChallenge() public { + function test_resolve_invalidInputData_reverts( + address challenger, + address resolver, + bytes memory preImage, + bytes memory wrongPreImage, + uint256 challengedBlockNumber, + uint256 resolverRefundPercentage, + uint128 txGasPrice + ) + public + { + // Assume neither the challenger nor resolver is address(0) and that they're not the same entity + vm.assume(challenger != address(0)); + vm.assume(resolver != address(0)); + vm.assume(challenger != resolver); + vm.assume(keccak256(preImage) != keccak256(wrongPreImage)); + + // Bound the resolver refund percentage to 100 + resolverRefundPercentage = bound(resolverRefundPercentage, 0, 100); + + // Set the gas price to a fuzzed value to test bond distribution logic + vm.txGasPrice(txGasPrice); + + // Change the resolver refund percentage + vm.prank(dataAvailabilityChallenge.owner()); + dataAvailabilityChallenge.setResolverRefundPercentage(resolverRefundPercentage); + + // Assume the block number is not close to the max uint256 value + vm.assume( + challengedBlockNumber + < type(uint256).max - dataAvailabilityChallenge.challengeWindow() + - dataAvailabilityChallenge.resolveWindow() + ); + bytes memory challengedCommitment = computeCommitmentKeccak256(wrongPreImage); + + // Move to block after challenged block + vm.roll(challengedBlockNumber + 1); + + // Challenge the hash + uint256 bondSize = dataAvailabilityChallenge.bondSize(); + vm.deal(challenger, bondSize); + vm.prank(challenger); + dataAvailabilityChallenge.challenge{ value: bondSize }(challengedBlockNumber, challengedCommitment); + + // Resolve the challenge + vm.prank(resolver); + vm.expectRevert( + abi.encodeWithSelector( + IDataAvailabilityChallenge.InvalidInputData.selector, + computeCommitmentKeccak256(preImage), + challengedCommitment + ) + ); + dataAvailabilityChallenge.resolve(challengedBlockNumber, challengedCommitment, preImage); + } + + function test_resolve_nonExistentChallenge_reverts() public { bytes memory preImage = "some preimage"; uint256 challengedBlockNumber = 1; @@ -295,7 +396,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.resolve(challengedBlockNumber, computeCommitmentKeccak256(preImage), preImage); } - function testResolveFailResolved() public { + function test_resolve_resolved_reverts() public { bytes memory preImage = "some preimage"; bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); uint256 challengedBlockNumber = 1; @@ -315,7 +416,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.resolve(challengedBlockNumber, challengedCommitment, preImage); } - function testResolveFailExpired() public { + function test_resolve_expired_reverts() public { bytes memory preImage = "some preimage"; bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); uint256 challengedBlockNumber = 1; @@ -335,7 +436,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.resolve(challengedBlockNumber, challengedCommitment, preImage); } - function testResolveFailAfterResolveWindow() public { + function test_resolve_afterResolveWindow_reverts() public { bytes memory preImage = "some preimage"; bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); uint256 challengedBlockNumber = 1; @@ -355,7 +456,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.resolve(challengedBlockNumber, challengedCommitment, preImage); } - function testUnlockBondSuccess(bytes memory preImage, uint256 challengedBlockNumber) public { + function test_unlockBond_succeeds(bytes memory preImage, uint256 challengedBlockNumber) public { // Assume the block number is not close to the max uint256 value vm.assume( challengedBlockNumber @@ -400,7 +501,7 @@ contract DataAvailabilityChallengeTest is CommonTest { assertEq(dataAvailabilityChallenge.balances(address(this)), balanceAfterUnlock); } - function testUnlockBondFailNonExistentChallenge() public { + function test_unlockBond_nonExistentChallenge_reverts() public { bytes memory preImage = "some preimage"; bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); uint256 challengedBlockNumber = 1; @@ -413,7 +514,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.unlockBond(challengedBlockNumber, challengedCommitment); } - function testUnlockBondFailResolvedChallenge() public { + function test_unlockBond_resolvedChallenge_reverts() public { bytes memory preImage = "some preimage"; bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); uint256 challengedBlockNumber = 1; @@ -433,7 +534,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.unlockBond(challengedBlockNumber, challengedCommitment); } - function testUnlockBondExpiredChallengeTwice() public { + function test_unlockBond_expiredChallengeTwice_fails() public { bytes memory preImage = "some preimage"; bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); uint256 challengedBlockNumber = 1; @@ -458,7 +559,7 @@ contract DataAvailabilityChallengeTest is CommonTest { assertEq(dataAvailabilityChallenge.balances(address(this)), balanceAfterUnlock); } - function testUnlockFailResolveWindowNotClosed() public { + function test_unlockBond_resolveWindowNotClosed_reverts() public { bytes memory preImage = "some preimage"; bytes memory challengedCommitment = computeCommitmentKeccak256(preImage); uint256 challengedBlockNumber = 1; @@ -477,7 +578,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.unlockBond(challengedBlockNumber, challengedCommitment); } - function testSetBondSize() public { + function test_setBondSize_succeeds() public { uint256 requiredBond = dataAvailabilityChallenge.bondSize(); uint256 actualBond = requiredBond - 1; dataAvailabilityChallenge.deposit{ value: actualBond }(); @@ -497,14 +598,14 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.challenge(0, challengedCommitment); } - function testSetResolverRefundPercentage(uint256 resolverRefundPercentage) public { + function test_setResolverRefundPercentage_succeeds(uint256 resolverRefundPercentage) public { resolverRefundPercentage = bound(resolverRefundPercentage, 0, 100); vm.prank(dataAvailabilityChallenge.owner()); dataAvailabilityChallenge.setResolverRefundPercentage(resolverRefundPercentage); assertEq(dataAvailabilityChallenge.resolverRefundPercentage(), resolverRefundPercentage); } - function testSetResolverRefundPercentageFail() public { + function test_setResolverRefundPercentage_invalidResolverRefundPercentage_reverts() public { address owner = dataAvailabilityChallenge.owner(); vm.expectRevert( abi.encodeWithSelector(IDataAvailabilityChallenge.InvalidResolverRefundPercentage.selector, 101) @@ -513,7 +614,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.setResolverRefundPercentage(101); } - function testSetBondSizeFailOnlyOwner(address notOwner, uint256 newBondSize) public { + function test_setBondSize_onlyOwner_reverts(address notOwner, uint256 newBondSize) public { vm.assume(notOwner != dataAvailabilityChallenge.owner()); // Expect setting the bond size to fail because the sender is not the owner @@ -522,7 +623,7 @@ contract DataAvailabilityChallengeTest is CommonTest { dataAvailabilityChallenge.setBondSize(newBondSize); } - function testValidateCommitment() public { + function test_validateCommitment_succeeds() public { // Should not revert given a valid commitment bytes memory validCommitment = abi.encodePacked(CommitmentType.Keccak256, keccak256("test")); dataAvailabilityChallenge.validateCommitment(validCommitment); diff --git a/packages/contracts-bedrock/test/L1/DelayedVetoable.t.sol b/packages/contracts-bedrock/test/L1/DelayedVetoable.t.sol deleted file mode 100644 index 4d33a0784972a..0000000000000 --- a/packages/contracts-bedrock/test/L1/DelayedVetoable.t.sol +++ /dev/null @@ -1,264 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { Test } from "forge-std/Test.sol"; -import { DelayedVetoable } from "src/L1/DelayedVetoable.sol"; -import { IDelayedVetoable } from "src/L1/interfaces/IDelayedVetoable.sol"; - -contract DelayedVetoable_Init is Test { - error Unauthorized(address expected, address actual); - error ForwardingEarly(); - - event Initiated(bytes32 indexed callHash, bytes data); - event Forwarded(bytes32 indexed callHash, bytes data); - event Vetoed(bytes32 indexed callHash, bytes data); - - address target; - address initiator; - address vetoer; - uint256 operatingDelay = 14 days; - IDelayedVetoable delayedVetoable; - - function setUp() public { - initiator = makeAddr("initiator"); - vetoer = makeAddr("vetoer"); - target = makeAddr("target"); - vm.deal(initiator, 10000 ether); - vm.deal(vetoer, 10000 ether); - - delayedVetoable = IDelayedVetoable( - address( - new DelayedVetoable({ - _initiator: initiator, - _vetoer: vetoer, - _target: address(target), - _operatingDelay: operatingDelay - }) - ) - ); - - // Most tests will use the operating delay, so we call as the initiator with null data - // to set the delay. For tests that need to use the initial zero delay, we'll modify the - // value in storage. - vm.prank(initiator); - (bool success,) = address(delayedVetoable).call(hex""); - assertTrue(success); - } - - /// @dev This function is used to prevent initiating the delay unintentionally. - /// It should only be used on tests prior to the delay being activated. - /// @param data The data to be used in the call. - function assumeNonzeroData(bytes memory data) internal pure { - vm.assume(data.length > 0); - } - - /// @dev This function is used to ensure that the data does not clash with the queuedAt function selector. - /// @param data The data to be used in the call. - function assumeNoClash(bytes calldata data) internal pure { - if (data.length >= 4) { - vm.assume(bytes4(data[0:4]) != bytes4(keccak256("queuedAt(bytes32)"))); - } - } -} - -contract DelayedVetoable_Getters_Test is DelayedVetoable_Init { - /// @dev The getters return the expected values when called by the zero address. - function test_getters() external { - vm.startPrank(address(0)); - assertEq(delayedVetoable.initiator(), initiator); - assertEq(delayedVetoable.vetoer(), vetoer); - assertEq(delayedVetoable.target(), target); - assertEq(delayedVetoable.delay(), operatingDelay); - assertEq(delayedVetoable.queuedAt(keccak256(abi.encode(0))), 0); - } -} - -contract DelayedVetoable_Getters_TestFail is DelayedVetoable_Init { - /// @dev Check that getter calls from unauthorized entities will revert. - function test_getters_notZeroAddress_reverts() external { - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); - delayedVetoable.initiator(); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); - delayedVetoable.vetoer(); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); - delayedVetoable.target(); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); - delayedVetoable.delay(); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); - delayedVetoable.queuedAt(keccak256(abi.encode(0))); - } -} - -contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init { - /// @dev A call can be initiated by the initiator. - function testFuzz_handleCall_initiation_succeeds(bytes calldata data) external { - assumeNoClash(data); - vm.expectEmit(true, false, false, true, address(delayedVetoable)); - emit Initiated(keccak256(data), data); - - vm.prank(initiator); - (bool success,) = address(delayedVetoable).call(data); - assertTrue(success); - } - - /// @dev The delay is inititially set to zero and the call is immediately forwarded. - function testFuzz_handleCall_initialForwardingImmediately_succeeds( - bytes calldata inData, - bytes calldata outData - ) - external - { - assumeNonzeroData(inData); - assumeNoClash(inData); - - // Reset the delay to zero - vm.store(address(delayedVetoable), bytes32(uint256(0)), bytes32(uint256(0))); - - vm.mockCall(target, inData, outData); - vm.expectEmit(true, false, false, true, address(delayedVetoable)); - vm.expectCall({ callee: target, data: inData }); - emit Forwarded(keccak256(inData), inData); - vm.prank(initiator); - (bool success, bytes memory returnData) = address(delayedVetoable).call(inData); - assertTrue(success); - assertEq(returnData, outData); - - // Check that the callHash is not stored for future forwarding - bytes32 callHash = keccak256(inData); - vm.prank(address(0)); - assertEq(delayedVetoable.queuedAt(callHash), 0); - } - - /// @dev Calls are not forwarded until the delay has passed. - function testFuzz_handleCall_forwardingWithDelay_succeeds(bytes calldata data) external { - assumeNonzeroData(data); - assumeNoClash(data); - - vm.prank(initiator); - (bool success,) = address(delayedVetoable).call(data); - - // Check that the call is in the _queuedAt mapping - bytes32 callHash = keccak256(data); - vm.prank(address(0)); - assertEq(delayedVetoable.queuedAt(callHash), block.timestamp); - - vm.warp(block.timestamp + operatingDelay); - vm.expectEmit(true, false, false, true, address(delayedVetoable)); - emit Forwarded(keccak256(data), data); - - vm.expectCall({ callee: target, data: data }); - (success,) = address(delayedVetoable).call(data); - assertTrue(success); - } -} - -contract DelayedVetoable_HandleCall_TestFail is DelayedVetoable_Init { - /// @dev Only the initiator can initiate a call. - function test_handleCall_unauthorizedInitiation_reverts() external { - vm.expectRevert(abi.encodeWithSelector(DelayedVetoable.Unauthorized.selector, initiator, address(this))); - (bool revertsAsExpected,) = address(delayedVetoable).call(hex"00001234"); - assertTrue(revertsAsExpected); - } - - /// @dev The call cannot be forwarded until the delay has passed. - function testFuzz_handleCall_forwardingTooSoon_reverts(bytes calldata data) external { - assumeNoClash(data); - vm.prank(initiator); - (bool success,) = address(delayedVetoable).call(data); - assertTrue(success); - - vm.expectRevert(DelayedVetoable.ForwardingEarly.selector); - (bool revertsAsExpected,) = address(delayedVetoable).call(data); - assertTrue(revertsAsExpected); - } - - /// @dev The call cannot be forwarded a second time. - function testFuzz_handleCall_forwardingTwice_reverts(bytes calldata data) external { - assumeNoClash(data); - - // Initiate the call - vm.prank(initiator); - (bool success,) = address(delayedVetoable).call(data); - assertTrue(success); - - vm.warp(block.timestamp + operatingDelay); - vm.expectEmit(true, false, false, true, address(delayedVetoable)); - emit Forwarded(keccak256(data), data); - - // Forward the call - vm.expectCall({ callee: target, data: data }); - (success,) = address(delayedVetoable).call(data); - assertTrue(success); - - // Attempt to forward the same call again. - vm.expectRevert(abi.encodeWithSelector(DelayedVetoable.Unauthorized.selector, initiator, address(this))); - (bool revertsAsExpected,) = address(delayedVetoable).call(data); - assertTrue(revertsAsExpected); - } - - /// @dev If the target reverts, it is bubbled up. - function testFuzz_handleCall_forwardingTargetReverts_reverts( - bytes calldata inData, - bytes calldata outData - ) - external - { - assumeNoClash(inData); - - // Initiate the call - vm.prank(initiator); - (bool success,) = address(delayedVetoable).call(inData); - assertTrue(success); - - vm.warp(block.timestamp + operatingDelay); - vm.expectEmit(true, false, false, true, address(delayedVetoable)); - emit Forwarded(keccak256(inData), inData); - - vm.mockCallRevert(target, inData, outData); - - // Forward the call - vm.expectRevert(outData); - (bool revertsAsExpected,) = address(delayedVetoable).call(inData); - assertTrue(revertsAsExpected); - } - - function testFuzz_handleCall_forwardingTargetRetValue_succeeds( - bytes calldata inData, - bytes calldata outData - ) - external - { - assumeNoClash(inData); - - // Initiate the call - vm.prank(initiator); - (bool success,) = address(delayedVetoable).call(inData); - assertTrue(success); - - vm.warp(block.timestamp + operatingDelay); - vm.expectEmit(true, false, false, true, address(delayedVetoable)); - emit Forwarded(keccak256(inData), inData); - - vm.mockCall(target, inData, outData); - - // Forward the call - (bool success2, bytes memory retData) = address(delayedVetoable).call(inData); - assertTrue(success2); - assertEq(keccak256(retData), keccak256(outData)); - } - - /// @dev A test documenting the single instance in which the contract is not 'transparent' to the initiator. - function testFuzz_handleCall_queuedAtClash_reverts() external { - // This will get us calldata with the same function selector as the queuedAt function, but - // with the incorrect input data length. - bytes memory inData = abi.encodePacked(keccak256("queuedAt(bytes32)")); - - // Reset the delay to zero - vm.store(address(delayedVetoable), bytes32(uint256(0)), bytes32(uint256(0))); - - vm.prank(initiator); - vm.expectRevert(bytes("")); - (bool revertsAsExpected,) = address(delayedVetoable).call(inData); - assertTrue(revertsAsExpected); - } -} diff --git a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol index 2c6b5b66456d3..d7c5dc29b3485 100644 --- a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.15; // Testing utilities -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; import { Reverter } from "test/mocks/Callers.sol"; +import { stdError } from "forge-std/StdError.sol"; // Libraries import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; @@ -12,12 +13,12 @@ import { Hashing } from "src/libraries/Hashing.sol"; import { Encoding } from "src/libraries/Encoding.sol"; // Target contract dependencies -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -contract L1CrossDomainMessenger_Test is Bridge_Initializer { +contract L1CrossDomainMessenger_Test is CommonTest { /// @dev The receiver address address recipient = address(0xabbaacdc); @@ -58,14 +59,16 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { // deposit transaction on the optimism portal should be called vm.expectCall( address(optimismPortal), - abi.encodeWithSelector( - IOptimismPortal.depositTransaction.selector, - Predeploys.L2_CROSS_DOMAIN_MESSENGER, - 0, - l1CrossDomainMessenger.baseGas(hex"ff", 100), - false, - Encoding.encodeCrossDomainMessage( - l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff" + abi.encodeCall( + IOptimismPortal.depositTransaction, + ( + Predeploys.L2_CROSS_DOMAIN_MESSENGER, + 0, + l1CrossDomainMessenger.baseGas(hex"ff", 100), + false, + Encoding.encodeCrossDomainMessage( + l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff" + ) ) ) ); @@ -171,21 +174,93 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { assertEq(l1CrossDomainMessenger.failedMessages(hash), false); } - /// @dev Tests that relayMessage reverts if attempting to relay a message - /// sent to an L1 system contract. - function test_relayMessage_toSystemContract_reverts() external { - // set the target to be the OptimismPortal - address target = address(optimismPortal); + /// @dev Tests that relayMessage reverts if caller is optimismPortal and the value sent does not match the amount + function test_relayMessage_fromOtherMessengerValueMismatch_reverts() external { + address target = alice; address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes memory message = hex"1111"; + // set the value of op.l2Sender() to be the L2CrossDomainMessenger. + vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + + // correctly sending as OptimismPortal but amount does not match msg.value + vm.deal(address(optimismPortal), 10 ether); + vm.prank(address(optimismPortal)); + vm.expectRevert(stdError.assertionError); + l1CrossDomainMessenger.relayMessage{ value: 10 ether }( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 9 ether, 0, message + ); + } + + /// @dev Tests that relayMessage reverts if a failed message is attempted to be replayed via the optimismPortal + function test_relayMessage_fromOtherMessengerFailedMessageReplay_reverts() external { + address target = alice; + address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; + bytes memory message = hex"1111"; + + // set the value of op.l2Sender() to be the L2 Cross Domain Messenger. + vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + + // make a failed message + vm.etch(target, hex"fe"); vm.prank(address(optimismPortal)); - vm.expectRevert("CrossDomainMessenger: message cannot be replayed"); l1CrossDomainMessenger.relayMessage( Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message ); - vm.store(address(optimismPortal), 0, bytes32(abi.encode(sender))); + // cannot replay messages when optimism portal is msg.sender + vm.prank(address(optimismPortal)); + vm.expectRevert(stdError.assertionError); + l1CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message + ); + } + + /// @dev Tests that relayMessage reverts if attempting to relay a message + /// with l1CrossDomainMessenger as the target + function test_relayMessage_toSelf_reverts() external { + address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; + bytes memory message = hex"1111"; + + vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + + vm.prank(address(optimismPortal)); + vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); + l1CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), + sender, + address(l1CrossDomainMessenger), + 0, + 0, + message + ); + } + + /// @dev Tests that relayMessage reverts if attempting to relay a message + /// with optimismPortal as the target + function test_relayMessage_toOptimismPortal_reverts() external { + address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; + bytes memory message = hex"1111"; + + vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + + vm.prank(address(optimismPortal)); + vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); + l1CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, address(optimismPortal), 0, 0, message + ); + } + + /// @dev Tests that the relayMessage function reverts if the message called by non-optimismPortal but not a failed + /// message + function test_relayMessage_relayingNewMessageByExternalUser_reverts() external { + address target = address(alice); + address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; + bytes memory message = hex"1111"; + + vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + + vm.prank(bob); vm.expectRevert("CrossDomainMessenger: message cannot be replayed"); l1CrossDomainMessenger.relayMessage( Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message @@ -604,7 +679,7 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { /// @dev Tests that the superchain config is called by the messengers paused function function test_pause_callsSuperchainConfig_succeeds() external { - vm.expectCall(address(superchainConfig), abi.encodeWithSelector(ISuperchainConfig.paused.selector)); + vm.expectCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ())); l1CrossDomainMessenger.paused(); } @@ -621,23 +696,25 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { } /// @dev Tests that sendMessage succeeds with a custom gas token when the call value is zero. - function test_sendMessage_customGasToken_noValue_succeeds() external { + function test_sendMessage_customGasTokenButNoValue_succeeds() external { // Mock the gasPayingToken function to return a custom gas token vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) ); // deposit transaction on the optimism portal should be called vm.expectCall( address(optimismPortal), - abi.encodeWithSelector( - IOptimismPortal.depositTransaction.selector, - Predeploys.L2_CROSS_DOMAIN_MESSENGER, - 0, - l1CrossDomainMessenger.baseGas(hex"ff", 100), - false, - Encoding.encodeCrossDomainMessage( - l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff" + abi.encodeCall( + IOptimismPortal.depositTransaction, + ( + Predeploys.L2_CROSS_DOMAIN_MESSENGER, + 0, + l1CrossDomainMessenger.baseGas(hex"ff", 100), + false, + Encoding.encodeCrossDomainMessage( + l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff" + ) ) ) ); @@ -667,10 +744,10 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { } /// @dev Tests that the sendMessage reverts when call value is non-zero with custom gas token. - function test_sendMessage_customGasToken_withValue_reverts() external { + function test_sendMessage_customGasTokenWithValue_reverts() external { // Mock the gasPayingToken function to return a custom gas token vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.expectRevert("CrossDomainMessenger: cannot send value with custom gas token"); @@ -678,10 +755,10 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { } /// @dev Tests that the relayMessage succeeds with a custom gas token when the call value is zero. - function test_relayMessage_customGasToken_noValue_succeeds() external { + function test_relayMessage_customGasTokenAndNoValue_succeeds() external { // Mock the gasPayingToken function to return a custom gas token vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); address target = address(0xabcd); @@ -718,10 +795,10 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { /// @dev Tests that the relayMessage reverts when call value is non-zero with custom gas token. /// The L2CrossDomainMessenger contract cannot `sendMessage` with value when using a custom gas token. - function test_relayMessage_customGasToken_withValue_reverts() external virtual { + function test_relayMessage_customGasTokenWithValue_reverts() external virtual { // Mock the gasPayingToken function to return a custom gas token vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.expectRevert("CrossDomainMessenger: value must be zero unless message is from a system address"); @@ -738,12 +815,12 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { /// @dev A regression test against a reentrancy vulnerability in the CrossDomainMessenger contract, which /// was possible by intercepting and sandwhiching a signed Safe Transaction to upgrade it. -contract L1CrossDomainMessenger_ReinitReentryTest is Bridge_Initializer { +contract L1CrossDomainMessenger_ReinitReentryTest is CommonTest { bool attacked; // Common values used across functions uint256 constant messageValue = 50; - bytes constant selector = abi.encodeWithSelector(this.reinitAndReenter.selector); + bytes selector = abi.encodeCall(this.reinitAndReenter, ()); address sender; bytes32 hash; address target; @@ -760,7 +837,7 @@ contract L1CrossDomainMessenger_ReinitReentryTest is Bridge_Initializer { /// @dev This method will be called by the relayed message, and will attempt to reenter the relayMessage function /// exactly once. - function reinitAndReenter() public payable { + function reinitAndReenter() external payable { // only attempt the attack once if (!attacked) { attacked = true; diff --git a/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol b/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol index 44feddc5031d4..88913a76ba37f 100644 --- a/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; // Testing -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Contracts import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -11,10 +11,10 @@ import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL2ERC721Bridge } from "interfaces/L2/IL2ERC721Bridge.sol"; /// @dev Test ERC721 contract. contract TestERC721 is ERC721 { @@ -25,7 +25,7 @@ contract TestERC721 is ERC721 { } } -contract L1ERC721Bridge_Test is Bridge_Initializer { +contract L1ERC721Bridge_Test is CommonTest { TestERC721 internal localToken; TestERC721 internal remoteToken; uint256 internal constant tokenId = 1; @@ -237,6 +237,14 @@ contract L1ERC721Bridge_Test is Bridge_Initializer { assertEq(localToken.ownerOf(tokenId), alice); } + /// @dev Tests that `bridgeERC721To` reverts if the to address is the zero address. + function test_bridgeERC721To_toZeroAddress_reverts() external { + // Bridge the token. + vm.prank(bob); + vm.expectRevert("ERC721Bridge: nft recipient cannot be address(0)"); + l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), address(0), tokenId, 1234, hex"5678"); + } + /// @dev Tests that the ERC721 bridge successfully finalizes a withdrawal. function test_finalizeBridgeERC721_succeeds() external { // Bridge the token. @@ -250,7 +258,7 @@ contract L1ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector(l1CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(Predeploys.L2_ERC721_BRIDGE) ); vm.prank(address(l1CrossDomainMessenger)); @@ -276,7 +284,7 @@ contract L1ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector(l1CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(alice) ); vm.prank(address(l1CrossDomainMessenger)); @@ -290,7 +298,7 @@ contract L1ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector(l1CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(Predeploys.L2_ERC721_BRIDGE) ); vm.prank(address(l1CrossDomainMessenger)); @@ -306,7 +314,7 @@ contract L1ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector(l1CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(Predeploys.L2_ERC721_BRIDGE) ); vm.prank(address(l1CrossDomainMessenger)); @@ -315,7 +323,7 @@ contract L1ERC721Bridge_Test is Bridge_Initializer { } } -contract L1ERC721Bridge_Pause_Test is Bridge_Initializer { +contract L1ERC721Bridge_Pause_Test is CommonTest { /// @dev Verifies that the `paused` accessor returns the same value as the `paused` function of the /// `superchainConfig`. function test_paused_succeeds() external view { @@ -325,7 +333,7 @@ contract L1ERC721Bridge_Pause_Test is Bridge_Initializer { /// @dev Ensures that the `paused` function of the bridge contract actually calls the `paused` function of the /// `superchainConfig`. function test_pause_callsSuperchainConfig_succeeds() external { - vm.expectCall(address(superchainConfig), abi.encodeWithSelector(ISuperchainConfig.paused.selector)); + vm.expectCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ())); l1ERC721Bridge.paused(); } @@ -343,7 +351,7 @@ contract L1ERC721Bridge_Pause_Test is Bridge_Initializer { } } -contract L1ERC721Bridge_Pause_TestFail is Bridge_Initializer { +contract L1ERC721Bridge_Pause_TestFail is CommonTest { /// @dev Sets up the test by pausing the bridge, giving ether to the bridge and mocking /// the calls to the xDomainMessageSender so that it returns the correct value. function setUp() public override { @@ -354,7 +362,7 @@ contract L1ERC721Bridge_Pause_TestFail is Bridge_Initializer { vm.mockCall( address(l1ERC721Bridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1ERC721Bridge.otherBridge())) ); } diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index f01572733bc4d..4cc1b8f35994a 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.15; // Testing import { stdStorage, StdStorage } from "forge-std/Test.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -14,12 +14,12 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; // Interfaces -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; -contract L1StandardBridge_Getter_Test is Bridge_Initializer { +contract L1StandardBridge_Getter_Test is CommonTest { /// @dev Test that the accessors return the correct initialized values. function test_getters_succeeds() external view { assert(l1StandardBridge.l2TokenBridge() == address(l2StandardBridge)); @@ -31,7 +31,7 @@ contract L1StandardBridge_Getter_Test is Bridge_Initializer { } } -contract L1StandardBridge_Initialize_Test is Bridge_Initializer { +contract L1StandardBridge_Initialize_Test is CommonTest { /// @dev Test that the constructor sets the correct values. /// @notice Marked virtual to be overridden in /// test/kontrol/deployment/DeploymentSummary.t.sol @@ -58,7 +58,7 @@ contract L1StandardBridge_Initialize_Test is Bridge_Initializer { } } -contract L1StandardBridge_Pause_Test is Bridge_Initializer { +contract L1StandardBridge_Pause_Test is CommonTest { /// @dev Verifies that the `paused` accessor returns the same value as the `paused` function of the /// `superchainConfig`. function test_paused_succeeds() external view { @@ -68,7 +68,7 @@ contract L1StandardBridge_Pause_Test is Bridge_Initializer { /// @dev Ensures that the `paused` function of the bridge contract actually calls the `paused` function of the /// `superchainConfig`. function test_pause_callsSuperchainConfig_succeeds() external { - vm.expectCall(address(superchainConfig), abi.encodeWithSelector(ISuperchainConfig.paused.selector)); + vm.expectCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ())); l1StandardBridge.paused(); } @@ -86,7 +86,7 @@ contract L1StandardBridge_Pause_Test is Bridge_Initializer { } } -contract L1StandardBridge_Pause_TestFail is Bridge_Initializer { +contract L1StandardBridge_Pause_TestFail is CommonTest { /// @dev Sets up the test by pausing the bridge, giving ether to the bridge and mocking /// the calls to the xDomainMessageSender so that it returns the correct value. function setUp() public override { @@ -99,7 +99,7 @@ contract L1StandardBridge_Pause_TestFail is Bridge_Initializer { vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.otherBridge())) ); } @@ -157,9 +157,9 @@ contract L1StandardBridge_Pause_TestFail is Bridge_Initializer { } } -contract L1StandardBridge_Initialize_TestFail is Bridge_Initializer { } +contract L1StandardBridge_Initialize_TestFail is CommonTest { } -contract L1StandardBridge_Receive_Test is Bridge_Initializer { +contract L1StandardBridge_Receive_Test is CommonTest { /// @dev Tests receive bridges ETH successfully. function test_receive_succeeds() external { assertEq(address(optimismPortal).balance, 0); @@ -173,11 +173,13 @@ contract L1StandardBridge_Receive_Test is Bridge_Initializer { vm.expectCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, - address(l2StandardBridge), - abi.encodeWithSelector(StandardBridge.finalizeBridgeETH.selector, alice, alice, 100, hex""), - 200_000 + abi.encodeCall( + ICrossDomainMessenger.sendMessage, + ( + address(l2StandardBridge), + abi.encodeCall(StandardBridge.finalizeBridgeETH, (alice, alice, 100, hex"")), + 200_000 + ) ) ); @@ -188,12 +190,12 @@ contract L1StandardBridge_Receive_Test is Bridge_Initializer { } } -contract L1StandardBridge_Receive_TestFail is Bridge_Initializer { +contract L1StandardBridge_Receive_TestFail is CommonTest { /// @dev Tests receive function reverts with custom gas token. function testFuzz_receive_customGasToken_reverts(uint256 _value) external { vm.prank(alice, alice); vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) ); vm.deal(alice, _value); (bool success, bytes memory data) = address(l1StandardBridge).call{ value: _value }(hex""); @@ -205,7 +207,7 @@ contract L1StandardBridge_Receive_TestFail is Bridge_Initializer { } } -contract PreBridgeETH is Bridge_Initializer { +contract PreBridgeETH is CommonTest { /// @dev Asserts the expected calls and events for bridging ETH depending /// on whether the bridge call is legacy or not. function _preBridgeETH(bool isLegacy, uint256 value) internal { @@ -214,51 +216,35 @@ contract PreBridgeETH is Bridge_Initializer { uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); - bytes memory message = - abi.encodeWithSelector(StandardBridge.finalizeBridgeETH.selector, alice, alice, value, hex"dead"); + bytes memory message = abi.encodeCall(StandardBridge.finalizeBridgeETH, (alice, alice, value, hex"dead")); if (isLegacy) { vm.expectCall( - address(l1StandardBridge), - value, - abi.encodeWithSelector(l1StandardBridge.depositETH.selector, 50000, hex"dead") + address(l1StandardBridge), value, abi.encodeCall(l1StandardBridge.depositETH, (50000, hex"dead")) ); } else { vm.expectCall( - address(l1StandardBridge), - value, - abi.encodeWithSelector(l1StandardBridge.bridgeETH.selector, 50000, hex"dead") + address(l1StandardBridge), value, abi.encodeCall(l1StandardBridge.bridgeETH, (50000, hex"dead")) ); } vm.expectCall( address(l1CrossDomainMessenger), value, - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, address(l2StandardBridge), message, 50000 - ) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l2StandardBridge), message, 50000)) ); - bytes memory innerMessage = abi.encodeWithSelector( - ICrossDomainMessenger.relayMessage.selector, - nonce, - address(l1StandardBridge), - address(l2StandardBridge), - value, - 50000, - message + bytes memory innerMessage = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + (nonce, address(l1StandardBridge), address(l2StandardBridge), value, 50000, message) ); uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 50000); vm.expectCall( address(optimismPortal), value, - abi.encodeWithSelector( - IOptimismPortal.depositTransaction.selector, - address(l2CrossDomainMessenger), - value, - baseGas, - false, - innerMessage + abi.encodeCall( + IOptimismPortal.depositTransaction, + (address(l2CrossDomainMessenger), value, baseGas, false, innerMessage) ) ); @@ -299,7 +285,7 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH { } } -contract L1StandardBridge_DepositETH_TestFail is Bridge_Initializer { +contract L1StandardBridge_DepositETH_TestFail is CommonTest { /// @dev Tests that depositing ETH reverts if the call is not from an EOA. function test_depositETH_notEoa_reverts() external { vm.etch(alice, address(L1Token).code); @@ -311,7 +297,7 @@ contract L1StandardBridge_DepositETH_TestFail is Bridge_Initializer { /// @dev Tests that depositing reverts with custom gas token. function test_depositETH_customGasToken_reverts() external { vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.prank(alice, alice); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); @@ -337,7 +323,7 @@ contract L1StandardBridge_BridgeETH_TestFail is PreBridgeETH { function test_bridgeETH_customGasToken_reverts() external { vm.prank(alice, alice); vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); @@ -345,7 +331,7 @@ contract L1StandardBridge_BridgeETH_TestFail is PreBridgeETH { } } -contract PreBridgeETHTo is Bridge_Initializer { +contract PreBridgeETHTo is CommonTest { /// @dev Asserts the expected calls and events for bridging ETH to a different /// address depending on whether the bridge call is legacy or not. function _preBridgeETHTo(bool isLegacy, uint256 value) internal { @@ -356,50 +342,34 @@ contract PreBridgeETHTo is Bridge_Initializer { if (isLegacy) { vm.expectCall( - address(l1StandardBridge), - value, - abi.encodeWithSelector(l1StandardBridge.depositETHTo.selector, bob, 60000, hex"dead") + address(l1StandardBridge), value, abi.encodeCall(l1StandardBridge.depositETHTo, (bob, 60000, hex"dead")) ); } else { vm.expectCall( - address(l1StandardBridge), - value, - abi.encodeWithSelector(l1StandardBridge.bridgeETHTo.selector, bob, 60000, hex"dead") + address(l1StandardBridge), value, abi.encodeCall(l1StandardBridge.bridgeETHTo, (bob, 60000, hex"dead")) ); } - bytes memory message = - abi.encodeWithSelector(StandardBridge.finalizeBridgeETH.selector, alice, bob, value, hex"dead"); + bytes memory message = abi.encodeCall(StandardBridge.finalizeBridgeETH, (alice, bob, value, hex"dead")); // the L1 bridge should call // L1CrossDomainMessenger.sendMessage vm.expectCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, address(l2StandardBridge), message, 60000 - ) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l2StandardBridge), message, 60000)) ); - bytes memory innerMessage = abi.encodeWithSelector( - ICrossDomainMessenger.relayMessage.selector, - nonce, - address(l1StandardBridge), - address(l2StandardBridge), - value, - 60000, - message + bytes memory innerMessage = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + (nonce, address(l1StandardBridge), address(l2StandardBridge), value, 60000, message) ); uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 60000); vm.expectCall( address(optimismPortal), - abi.encodeWithSelector( - IOptimismPortal.depositTransaction.selector, - address(l2CrossDomainMessenger), - value, - baseGas, - false, - innerMessage + abi.encodeCall( + IOptimismPortal.depositTransaction, + (address(l2CrossDomainMessenger), value, baseGas, false, innerMessage) ) ); @@ -441,7 +411,7 @@ contract L1StandardBridge_DepositETHTo_Test is PreBridgeETHTo { } } -contract L1StandardBridge_DepositETHTo_TestFail is Bridge_Initializer { +contract L1StandardBridge_DepositETHTo_TestFail is CommonTest { /// @dev Tests that depositETHTo reverts with custom gas token. function testFuzz_depositETHTo_customGasToken_reverts( uint256 _value, @@ -452,7 +422,7 @@ contract L1StandardBridge_DepositETHTo_TestFail is Bridge_Initializer { external { vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.deal(address(this), _value); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); @@ -484,7 +454,7 @@ contract L1StandardBridge_BridgeETHTo_TestFail is PreBridgeETHTo { external { vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.deal(address(this), _value); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); @@ -493,7 +463,7 @@ contract L1StandardBridge_BridgeETHTo_TestFail is PreBridgeETHTo { } } -contract L1StandardBridge_DepositERC20_Test is Bridge_Initializer { +contract L1StandardBridge_DepositERC20_Test is CommonTest { using stdStorage for StdStorage; // depositERC20 @@ -518,42 +488,28 @@ contract L1StandardBridge_DepositERC20_Test is Bridge_Initializer { L1Token.approve(address(l1StandardBridge), type(uint256).max); // The l1StandardBridge should transfer alice's tokens to itself - vm.expectCall( - address(L1Token), abi.encodeWithSelector(ERC20.transferFrom.selector, alice, address(l1StandardBridge), 100) - ); + vm.expectCall(address(L1Token), abi.encodeCall(ERC20.transferFrom, (alice, address(l1StandardBridge), 100))); - bytes memory message = abi.encodeWithSelector( - StandardBridge.finalizeBridgeERC20.selector, address(L2Token), address(L1Token), alice, alice, 100, hex"" + bytes memory message = abi.encodeCall( + StandardBridge.finalizeBridgeERC20, (address(L2Token), address(L1Token), alice, alice, 100, hex"") ); // the L1 bridge should call L1CrossDomainMessenger.sendMessage vm.expectCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, address(l2StandardBridge), message, 10000 - ) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l2StandardBridge), message, 10000)) ); - bytes memory innerMessage = abi.encodeWithSelector( - ICrossDomainMessenger.relayMessage.selector, - nonce, - address(l1StandardBridge), - address(l2StandardBridge), - 0, - 10000, - message + bytes memory innerMessage = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + (nonce, address(l1StandardBridge), address(l2StandardBridge), 0, 10000, message) ); uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 10000); vm.expectCall( address(optimismPortal), - abi.encodeWithSelector( - IOptimismPortal.depositTransaction.selector, - address(l2CrossDomainMessenger), - 0, - baseGas, - false, - innerMessage + abi.encodeCall( + IOptimismPortal.depositTransaction, (address(l2CrossDomainMessenger), 0, baseGas, false, innerMessage) ) ); @@ -584,7 +540,7 @@ contract L1StandardBridge_DepositERC20_Test is Bridge_Initializer { } } -contract L1StandardBridge_DepositERC20_TestFail is Bridge_Initializer { +contract L1StandardBridge_DepositERC20_TestFail is CommonTest { /// @dev Tests that depositing an ERC20 to the bridge reverts /// if the caller is not an EOA. function test_depositERC20_notEoa_reverts() external { @@ -597,7 +553,7 @@ contract L1StandardBridge_DepositERC20_TestFail is Bridge_Initializer { } } -contract L1StandardBridge_DepositERC20To_Test is Bridge_Initializer { +contract L1StandardBridge_DepositERC20To_Test is CommonTest { /// @dev Tests that depositing ERC20 to the bridge succeeds when /// sent to a different address. /// Bridge deposits are updated. @@ -609,18 +565,13 @@ contract L1StandardBridge_DepositERC20To_Test is Bridge_Initializer { uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); - bytes memory message = abi.encodeWithSelector( - StandardBridge.finalizeBridgeERC20.selector, address(L2Token), address(L1Token), alice, bob, 1000, hex"" + bytes memory message = abi.encodeCall( + StandardBridge.finalizeBridgeERC20, (address(L2Token), address(L1Token), alice, bob, 1000, hex"") ); - bytes memory innerMessage = abi.encodeWithSelector( - ICrossDomainMessenger.relayMessage.selector, - nonce, - address(l1StandardBridge), - address(l2StandardBridge), - 0, - 10000, - message + bytes memory innerMessage = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + (nonce, address(l1StandardBridge), address(l2StandardBridge), 0, 10000, message) ); uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 10000); @@ -653,26 +604,16 @@ contract L1StandardBridge_DepositERC20To_Test is Bridge_Initializer { // the L1 bridge should call L1CrossDomainMessenger.sendMessage vm.expectCall( address(l1CrossDomainMessenger), - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, address(l2StandardBridge), message, 10000 - ) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l2StandardBridge), message, 10000)) ); // The L1 XDM should call OptimismPortal.depositTransaction vm.expectCall( address(optimismPortal), - abi.encodeWithSelector( - IOptimismPortal.depositTransaction.selector, - address(l2CrossDomainMessenger), - 0, - baseGas, - false, - innerMessage + abi.encodeCall( + IOptimismPortal.depositTransaction, (address(l2CrossDomainMessenger), 0, baseGas, false, innerMessage) ) ); - vm.expectCall( - address(L1Token), - abi.encodeWithSelector(ERC20.transferFrom.selector, alice, address(l1StandardBridge), 1000) - ); + vm.expectCall(address(L1Token), abi.encodeCall(ERC20.transferFrom, (alice, address(l1StandardBridge), 1000))); vm.prank(alice); l1StandardBridge.depositERC20To(address(L1Token), address(L2Token), bob, 1000, 10000, hex""); @@ -681,7 +622,7 @@ contract L1StandardBridge_DepositERC20To_Test is Bridge_Initializer { } } -contract L1StandardBridge_FinalizeETHWithdrawal_Test is Bridge_Initializer { +contract L1StandardBridge_FinalizeETHWithdrawal_Test is CommonTest { using stdStorage for StdStorage; /// @dev Tests that finalizing an ETH withdrawal succeeds. @@ -700,7 +641,7 @@ contract L1StandardBridge_FinalizeETHWithdrawal_Test is Bridge_Initializer { vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); // ensure that the messenger has ETH to call with @@ -713,7 +654,7 @@ contract L1StandardBridge_FinalizeETHWithdrawal_Test is Bridge_Initializer { } } -contract L1StandardBridge_FinalizeETHWithdrawal_TestFail is Bridge_Initializer { +contract L1StandardBridge_FinalizeETHWithdrawal_TestFail is CommonTest { /// @dev Tests that finalizeETHWithdrawal reverts with custom gas token. function testFuzz_finalizeETHWithdrawal_customGasToken_reverts( uint256 _value, @@ -722,11 +663,11 @@ contract L1StandardBridge_FinalizeETHWithdrawal_TestFail is Bridge_Initializer { external { vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.deal(address(l1StandardBridge.messenger()), _value); @@ -737,7 +678,7 @@ contract L1StandardBridge_FinalizeETHWithdrawal_TestFail is Bridge_Initializer { } } -contract L1StandardBridge_FinalizeERC20Withdrawal_Test is Bridge_Initializer { +contract L1StandardBridge_FinalizeERC20Withdrawal_Test is CommonTest { using stdStorage for StdStorage; /// @dev Tests that finalizing an ERC20 withdrawal succeeds. @@ -761,11 +702,11 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is Bridge_Initializer { vm.expectEmit(address(l1StandardBridge)); emit ERC20BridgeFinalized(address(L1Token), address(L2Token), alice, alice, 100, hex""); - vm.expectCall(address(L1Token), abi.encodeWithSelector(ERC20.transfer.selector, alice, 100)); + vm.expectCall(address(L1Token), abi.encodeCall(ERC20.transfer, (alice, 100))); vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.prank(address(l1StandardBridge.messenger())); @@ -776,12 +717,12 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is Bridge_Initializer { } } -contract L1StandardBridge_FinalizeERC20Withdrawal_TestFail is Bridge_Initializer { +contract L1StandardBridge_FinalizeERC20Withdrawal_TestFail is CommonTest { /// @dev Tests that finalizing an ERC20 withdrawal reverts if the caller is not the L2 bridge. function test_finalizeERC20Withdrawal_notMessenger_reverts() external { vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.prank(address(28)); @@ -793,7 +734,7 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_TestFail is Bridge_Initializer function test_finalizeERC20Withdrawal_notOtherBridge_reverts() external { vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(address(0))) ); vm.prank(address(l1StandardBridge.messenger())); @@ -802,13 +743,13 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_TestFail is Bridge_Initializer } } -contract L1StandardBridge_FinalizeBridgeETH_Test is Bridge_Initializer { +contract L1StandardBridge_FinalizeBridgeETH_Test is CommonTest { /// @dev Tests that finalizing bridged ETH succeeds. function test_finalizeBridgeETH_succeeds() external { address messenger = address(l1StandardBridge.messenger()); vm.mockCall( messenger, - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.deal(messenger, 100); @@ -821,18 +762,18 @@ contract L1StandardBridge_FinalizeBridgeETH_Test is Bridge_Initializer { } } -contract L1StandardBridge_FinalizeBridgeETH_TestFail is Bridge_Initializer { +contract L1StandardBridge_FinalizeBridgeETH_TestFail is CommonTest { /// @dev Tests that finalizing bridged reverts with custom gas token. function testFuzz_finalizeBridgeETH_customGasToken_reverts(uint256 _value, bytes calldata _extraData) external { vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.deal(address(l1CrossDomainMessenger), _value); vm.prank(address(l1CrossDomainMessenger)); vm.mockCall( - address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2)) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2)) ); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); @@ -844,7 +785,7 @@ contract L1StandardBridge_FinalizeBridgeETH_TestFail is Bridge_Initializer { address messenger = address(l1StandardBridge.messenger()); vm.mockCall( messenger, - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.deal(messenger, 100); @@ -858,7 +799,7 @@ contract L1StandardBridge_FinalizeBridgeETH_TestFail is Bridge_Initializer { address messenger = address(l1StandardBridge.messenger()); vm.mockCall( messenger, - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.deal(messenger, 100); @@ -872,7 +813,7 @@ contract L1StandardBridge_FinalizeBridgeETH_TestFail is Bridge_Initializer { address messenger = address(l1StandardBridge.messenger()); vm.mockCall( messenger, - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.deal(messenger, 100); diff --git a/packages/contracts-bedrock/test/L1/L2OutputOracle.t.sol b/packages/contracts-bedrock/test/L1/L2OutputOracle.t.sol index 035fffd263d39..e4d53ddd9e782 100644 --- a/packages/contracts-bedrock/test/L1/L2OutputOracle.t.sol +++ b/packages/contracts-bedrock/test/L1/L2OutputOracle.t.sol @@ -15,9 +15,14 @@ import { Proxy } from "src/universal/Proxy.sol"; // Target contract import { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; + +contract L2OutputOracle_TestBase is CommonTest { + function setUp() public override { + super.enableLegacyContracts(); + super.setUp(); + } -contract L2OutputOracle_constructor_Test is CommonTest { /// @dev Tests that constructor sets the initial values correctly. function test_constructor_succeeds() external { IL2OutputOracle oracleImpl = IL2OutputOracle(address(new L2OutputOracle())); @@ -63,7 +68,7 @@ contract L2OutputOracle_constructor_Test is CommonTest { } } -contract L2OutputOracle_getter_Test is CommonTest { +contract L2OutputOracle_getter_Test is L2OutputOracle_TestBase { bytes32 proposedOutput1 = keccak256(abi.encode(1)); /// @dev Tests that `latestBlockNumber` returns the correct value. @@ -94,6 +99,31 @@ contract L2OutputOracle_getter_Test is CommonTest { l2OutputOracle.getL2Output(nextOutputIndex + 1); } + /// @dev Tests that `getL2OutputAfter` of an L2 block number returns the L2 output of the `getL2OutputIndexAfter` of + /// that block number. + function test_getL2OutputAfter_succeeds() external { + uint8 iterations = 5; + + Types.OutputProposal memory output; + Types.OutputProposal memory expectedOutput; + + for (uint8 i; i < iterations; i++) { + proposeAnotherOutput(); + } + + uint256 latestBlockNumber = l2OutputOracle.latestBlockNumber(); + for (uint8 i = iterations - 1; i > 0; i--) { + uint256 index = l2OutputOracle.getL2OutputIndexAfter(latestBlockNumber); + output = l2OutputOracle.getL2OutputAfter(latestBlockNumber); + expectedOutput = l2OutputOracle.getL2Output(index); + assertEq(output.outputRoot, expectedOutput.outputRoot); + assertEq(output.timestamp, expectedOutput.timestamp); + assertEq(output.l2BlockNumber, expectedOutput.l2BlockNumber); + + latestBlockNumber -= l2OutputOracle.SUBMISSION_INTERVAL(); + } + } + /// @dev Tests that `getL2OutputIndexAfter` returns the correct value /// when the input is the exact block number of the proposal. function test_getL2OutputIndexAfter_sameBlock_succeeds() external { @@ -106,6 +136,10 @@ contract L2OutputOracle_getter_Test is CommonTest { // Querying with exact same block as proposed returns the proposal. uint256 index1 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber1); assertEq(index1, 0); + assertEq( + keccak256(abi.encode(l2OutputOracle.getL2Output(index1))), + keccak256(abi.encode(output1, block.timestamp, nextBlockNumber1)) + ); } /// @dev Tests that `getL2OutputIndexAfter` returns the correct value @@ -120,6 +154,10 @@ contract L2OutputOracle_getter_Test is CommonTest { // Querying with previous block returns the proposal too. uint256 index1 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber1 - 1); assertEq(index1, 0); + assertEq( + keccak256(abi.encode(l2OutputOracle.getL2Output(index1))), + keccak256(abi.encode(output1, block.timestamp, nextBlockNumber1)) + ); } /// @dev Tests that `getL2OutputIndexAfter` returns the correct value. @@ -151,14 +189,26 @@ contract L2OutputOracle_getter_Test is CommonTest { // Querying with a block number between the first and second proposal uint256 index1 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber1 + 1); assertEq(index1, 1); + assertEq( + keccak256(abi.encode(l2OutputOracle.getL2Output(index1))), + keccak256(abi.encode(output2, l2OutputOracle.computeL2Timestamp(nextBlockNumber2) + 1, nextBlockNumber2)) + ); // Querying with a block number between the second and third proposal uint256 index2 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber2 + 1); assertEq(index2, 2); + assertEq( + keccak256(abi.encode(l2OutputOracle.getL2Output(index2))), + keccak256(abi.encode(output3, l2OutputOracle.computeL2Timestamp(nextBlockNumber3) + 1, nextBlockNumber3)) + ); // Querying with a block number between the third and fourth proposal uint256 index3 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber3 + 1); assertEq(index3, 3); + assertEq( + keccak256(abi.encode(l2OutputOracle.getL2Output(index3))), + keccak256(abi.encode(output4, l2OutputOracle.computeL2Timestamp(nextBlockNumber4) + 1, nextBlockNumber4)) + ); } /// @dev Tests that `getL2OutputIndexAfter` reverts when no output exists. @@ -199,7 +249,7 @@ contract L2OutputOracle_getter_Test is CommonTest { } } -contract L2OutputOracle_proposeL2Output_Test is CommonTest { +contract L2OutputOracle_proposeL2Output_Test is L2OutputOracle_TestBase { /// @dev Test that `proposeL2Output` succeeds for a valid input /// and when a block hash and number are not specified. function test_proposeL2Output_proposeAnotherOutput_succeeds() public { @@ -291,7 +341,7 @@ contract L2OutputOracle_proposeL2Output_Test is CommonTest { } } -contract L2OutputOracle_deleteOutputs_Test is CommonTest { +contract L2OutputOracle_deleteOutputs_Test is L2OutputOracle_TestBase { /// @dev Tests that `deleteL2Outputs` succeeds for a single output. function test_deleteOutputs_singleOutput_succeeds() external { proposeAnotherOutput(); @@ -405,7 +455,7 @@ contract L2OutputOracle_deleteOutputs_Test is CommonTest { } } -contract L2OutputOracleUpgradeable_Test is CommonTest { +contract L2OutputOracleUpgradeable_Test is L2OutputOracle_TestBase { /// @dev Tests that the proxy can be successfully upgraded. function test_upgrading_succeeds() external { Proxy proxy = Proxy(deploy.mustGetAddress("L2OutputOracleProxy")); @@ -417,7 +467,7 @@ contract L2OutputOracleUpgradeable_Test is CommonTest { vm.startPrank(EIP1967Helper.getAdmin(address(proxy))); // Reviewer note: the NextImpl() still uses reinitializer. If we want to remove that, we'll need to use a // two step upgrade with the Storage lib. - proxy.upgradeToAndCall(address(nextImpl), abi.encodeWithSelector(NextImpl.initialize.selector, 2)); + proxy.upgradeToAndCall(address(nextImpl), abi.encodeCall(NextImpl.initialize, (2))); assertEq(proxy.implementation(), address(nextImpl)); // Verify that the NextImpl contract initialized its values according as expected diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index fb008d4aa8d0c..418eebd9e64b6 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -3,20 +3,23 @@ pragma solidity 0.8.15; import { Test, stdStorage, StdStorage } from "forge-std/Test.sol"; -import { DeployOPChainInput } from "scripts/DeployOPChain.s.sol"; +import { DeployOPChainInput } from "scripts/deploy/DeployOPChain.s.sol"; import { DeployOPChain_TestBase } from "test/opcm/DeployOPChain.t.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; // Exposes internal functions for testing. contract OPContractsManager_Harness is OPContractsManager { constructor( ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions + IProtocolVersions _protocolVersions, + string memory _l1ContractsRelease, + Blueprints memory _blueprints, + Implementations memory _implementations ) - OPContractsManager(_superchainConfig, _protocolVersions) + OPContractsManager(_superchainConfig, _protocolVersions, _l1ContractsRelease, _blueprints, _implementations) { } function chainIdToBatchInboxAddress_exposed(uint256 l2ChainId) public pure returns (address) { @@ -49,7 +52,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { doi.set(doi.basefeeScalar.selector, basefeeScalar); doi.set(doi.blobBaseFeeScalar.selector, blobBaseFeeScalar); doi.set(doi.l2ChainId.selector, l2ChainId); - doi.set(doi.opcmProxy.selector, address(opcm)); + doi.set(doi.opcm.selector, address(opcm)); doi.set(doi.gasLimit.selector, gasLimit); doi.set(doi.disputeGameType.selector, disputeGameType); @@ -116,12 +119,17 @@ contract OPContractsManager_InternalMethods_Test is Test { function setUp() public { ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfig")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersions")); + OPContractsManager.Blueprints memory emptyBlueprints; + OPContractsManager.Implementations memory emptyImpls; vm.etch(address(superchainConfigProxy), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01"); opcmHarness = new OPContractsManager_Harness({ _superchainConfig: superchainConfigProxy, - _protocolVersions: protocolVersionsProxy + _protocolVersions: protocolVersionsProxy, + _l1ContractsRelease: "dev", + _blueprints: emptyBlueprints, + _implementations: emptyImpls }); } diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol index 6861a569c20b2..b575fdacff418 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol @@ -22,11 +22,11 @@ import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import "src/libraries/PortalErrors.sol"; // Interfaces -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; contract OptimismPortal_Test is CommonTest { address depositor; @@ -34,6 +34,7 @@ contract OptimismPortal_Test is CommonTest { /// @notice Marked virtual to be overridden in /// test/kontrol/deployment/DeploymentSummary.t.sol function setUp() public virtual override { + super.enableLegacyContracts(); super.setUp(); depositor = makeAddr("depositor"); } @@ -402,7 +403,7 @@ contract OptimismPortal_Test is CommonTest { uint256 ts = block.timestamp; vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSelector(IL2OutputOracle.getL2Output.selector), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), abi.encode(Types.OutputProposal(bytes32(uint256(1)), uint128(ts), uint128(startingBlockNumber))) ); @@ -534,7 +535,7 @@ contract OptimismPortal_Test is CommonTest { } function test_depositERC20Transaction_balanceOverflow_reverts() external { - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(42), 18)); + vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(42), 18)); // The balance slot vm.store(address(optimismPortal), bytes32(uint256(61)), bytes32(type(uint256).max)); @@ -580,6 +581,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // Use a constructor to set the storage vars above, so as to minimize the number of ffi calls. constructor() { + super.enableLegacyContracts(); super.setUp(); _defaultTx = Types.WithdrawalTransaction({ nonce: 0, @@ -592,7 +594,6 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // Get withdrawal proof data we can use for testing. (_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) = ffi.getProveWithdrawalTransactionInputs(_defaultTx); - // Setup a dummy output root proof for reuse. _outputRootProof = Types.OutputRootProof({ version: bytes32(uint256(0)), @@ -600,6 +601,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { messagePasserStorageRoot: _storageRoot, latestBlockhash: bytes32(uint256(0)) }); + _proposedBlockNumber = l2OutputOracle.nextBlockNumber(); _proposedOutputIndex = l2OutputOracle.nextOutputIndex(); } @@ -767,7 +769,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { } /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. - function test_finalizeWithdrawalTransaction_provenWithdrawalHash_ether_succeeds() external { + function test_finalizeWithdrawalTransaction_provenWithdrawalHashEther_succeeds() external { uint256 bobBalanceBefore = address(bob).balance; vm.expectEmit(true, true, true, true); @@ -783,10 +785,10 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { } /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. - function test_finalizeWithdrawalTransaction_provenWithdrawalHash_nonEther_targetToken_reverts() external { + function test_finalizeWithdrawalTransaction_provenWithdrawalHashNonEtherTargetToken_reverts() external { vm.mockCall( address(systemConfig), - abi.encodeWithSignature("gasPayingToken()"), + abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(_defaultTx.target), 18) ); @@ -830,7 +832,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // this case we just use bytes32(uint256(1)). vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSelector(IL2OutputOracle.getL2Output.selector), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), abi.encode(bytes32(uint256(1)), _proposedBlockNumber) ); @@ -856,7 +858,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // Mock a startingTimestamp change on the L2 Oracle vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSignature("startingTimestamp()"), + abi.encodeCall(IL2OutputOracle.startingTimestamp, ()), abi.encode(block.timestamp + 1) ); @@ -885,7 +887,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // to finalize the withdrawal. vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSelector(IL2OutputOracle.getL2Output.selector), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), abi.encode( Types.OutputProposal(bytes32(uint256(0)), uint128(block.timestamp), uint128(_proposedBlockNumber)) ) @@ -916,7 +918,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // finalization period. vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSelector(IL2OutputOracle.getL2Output.selector), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), abi.encode(Types.OutputProposal(_outputRoot, uint128(block.timestamp + 1), uint128(_proposedBlockNumber))) ); @@ -928,7 +930,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { assertEq(bobBalanceBefore, address(bob).balance); } - /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the target reverts. + /// @dev Tests that `finalizeWithdrawalTransaction` fails if the target reverts. function test_finalizeWithdrawalTransaction_targetFails_fails() external { uint256 bobBalanceBefore = address(bob).balance; vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. @@ -945,6 +947,142 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { assert(address(bob).balance == bobBalanceBefore); } + /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the target reverts and caller is the + /// ESTIMATION_ADDRESS. + function test_finalizeWithdrawalTransaction_targetFailsAndCallerIsEstimationAddress_reverts() external { + vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. + + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash, alice, bob); + optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); + + vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); + + vm.startPrank(Constants.ESTIMATION_ADDRESS, Constants.ESTIMATION_ADDRESS); + vm.expectRevert(GasEstimation.selector); + optimismPortal.finalizeWithdrawalTransaction(_defaultTx); + } + + /// @dev Tests that `finalizeWithdrawalTransaction` succeeds when _tx.data is empty. + function test_finalizeWithdrawalTransaction_noTxData_succeeds() external { + Types.WithdrawalTransaction memory _defaultTx_noData = Types.WithdrawalTransaction({ + nonce: 0, + sender: alice, + target: bob, + value: 100, + gasLimit: 100_000, + data: hex"" + }); + // Get withdrawal proof data we can use for testing. + ( + bytes32 _stateRoot_noData, + bytes32 _storageRoot_noData, + bytes32 _outputRoot_noData, + bytes32 _withdrawalHash_noData, + bytes[] memory _withdrawalProof_noData + ) = ffi.getProveWithdrawalTransactionInputs(_defaultTx_noData); + // Setup a dummy output root proof for reuse. + Types.OutputRootProof memory _outputRootProof_noData = Types.OutputRootProof({ + version: bytes32(uint256(0)), + stateRoot: _stateRoot_noData, + messagePasserStorageRoot: _storageRoot_noData, + latestBlockhash: bytes32(uint256(0)) + }); + + // Configure the oracle to return the output root we've prepared. + vm.mockCall( + address(l2OutputOracle), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), + abi.encode( + Types.OutputProposal( + _outputRoot_noData, + l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp, + uint128(_proposedBlockNumber) + ) + ) + ); + + // Fund the portal so that we can withdraw ETH. + vm.store(address(optimismPortal), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); + vm.deal(address(optimismPortal), 0xFFFFFFFF); + uint256 bobBalanceBefore = bob.balance; + + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash_noData, alice, bob); + optimismPortal.proveWithdrawalTransaction( + _defaultTx_noData, _proposedOutputIndex, _outputRootProof_noData, _withdrawalProof_noData + ); + + vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); + vm.expectEmit(true, true, false, true); + emit WithdrawalFinalized(_withdrawalHash_noData, true); + optimismPortal.finalizeWithdrawalTransaction(_defaultTx_noData); + + assertEq(bob.balance, bobBalanceBefore + 100); + } + + /// @dev Tests that `finalizeWithdrawalTransaction` succeeds when _tx.data is empty and with a custom gas token. + function test_finalizeWithdrawalTransaction_noTxDataNonEtherGasToken_succeeds() external { + Types.WithdrawalTransaction memory _defaultTx_noData = Types.WithdrawalTransaction({ + nonce: 0, + sender: alice, + target: bob, + value: 100, + gasLimit: 100_000, + data: hex"" + }); + // Get withdrawal proof data we can use for testing. + ( + bytes32 _stateRoot_noData, + bytes32 _storageRoot_noData, + bytes32 _outputRoot_noData, + bytes32 _withdrawalHash_noData, + bytes[] memory _withdrawalProof_noData + ) = ffi.getProveWithdrawalTransactionInputs(_defaultTx_noData); + // Setup a dummy output root proof for reuse. + Types.OutputRootProof memory _outputRootProof_noData = Types.OutputRootProof({ + version: bytes32(uint256(0)), + stateRoot: _stateRoot_noData, + messagePasserStorageRoot: _storageRoot_noData, + latestBlockhash: bytes32(uint256(0)) + }); + + // Configure the oracle to return the output root we've prepared. + vm.mockCall( + address(l2OutputOracle), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), + abi.encode( + Types.OutputProposal( + _outputRoot_noData, + l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp, + uint128(_proposedBlockNumber) + ) + ) + ); + + // Fund the portal so that we can withdraw ETH. + vm.store(address(optimismPortal), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); + deal(address(L1Token), address(optimismPortal), 0xFFFFFFFF); + // modify the gas token to be non ether + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(L1Token), 18) + ); + uint256 bobBalanceBefore = L1Token.balanceOf(bob); + + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash_noData, alice, bob); + optimismPortal.proveWithdrawalTransaction( + _defaultTx_noData, _proposedOutputIndex, _outputRootProof_noData, _withdrawalProof_noData + ); + + vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); + vm.expectEmit(true, true, false, true); + emit WithdrawalFinalized(_withdrawalHash_noData, true); + optimismPortal.finalizeWithdrawalTransaction(_defaultTx_noData); + + assertEq(L1Token.balanceOf(bob), bobBalanceBefore + 100); + } + /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the finalization period /// has not yet passed. function test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() external { @@ -952,7 +1090,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { uint256 recentTimestamp = block.timestamp - 1; vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSelector(IL2OutputOracle.getL2Output.selector), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), abi.encode(Types.OutputProposal(_outputRoot, uint128(recentTimestamp), uint128(_proposedBlockNumber))) ); @@ -1004,7 +1142,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSelector(IL2OutputOracle.getL2Output.selector), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), abi.encode( Types.OutputProposal( Hashing.hashOutputRootProof(outputRootProof), @@ -1032,7 +1170,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // this contract's callPortalAndExpectRevert() function above. Types.WithdrawalTransaction memory _testTx = _defaultTx; _testTx.target = address(this); - _testTx.data = abi.encodeWithSelector(this.callPortalAndExpectRevert.selector); + _testTx.data = abi.encodeCall(this.callPortalAndExpectRevert, ()); // Get modified proof inputs. ( @@ -1053,7 +1191,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { uint256 finalizedTimestamp = block.timestamp - l2OutputOracle.FINALIZATION_PERIOD_SECONDS() - 1; vm.mockCall( address(optimismPortal.l2Oracle()), - abi.encodeWithSelector(IL2OutputOracle.getL2Output.selector), + abi.encodePacked(IL2OutputOracle.getL2Output.selector), abi.encode(Types.OutputProposal(outputRoot, uint128(finalizedTimestamp), uint128(_proposedBlockNumber))) ); @@ -1127,7 +1265,7 @@ contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { // Setup the Oracle to return the outputRoot vm.mockCall( address(l2OutputOracle), - abi.encodeWithSelector(l2OutputOracle.getL2Output.selector), + abi.encodePacked(l2OutputOracle.getL2Output.selector), abi.encode(outputRoot, block.timestamp, 100) ); @@ -1174,7 +1312,7 @@ contract OptimismPortalUpgradeable_Test is CommonTest { // The value passed to the initialize must be larger than the last value // that initialize was called with. IProxy(payable(address(optimismPortal))).upgradeToAndCall( - address(nextImpl), abi.encodeWithSelector(NextImpl.initialize.selector, 2) + address(nextImpl), abi.encodeCall(NextImpl.initialize, (2)) ); assertEq(IProxy(payable(address(optimismPortal))).implementation(), address(nextImpl)); @@ -1212,8 +1350,13 @@ contract OptimismPortalResourceFuzz_Test is CommonTest { uint64 gasLimit = systemConfig.gasLimit(); // Bound resource config + _systemTxMaxGas = uint32(bound(_systemTxMaxGas, 0, gasLimit - 21000)); _maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, MAX_GAS_LIMIT / 8)); + _maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, gasLimit - _systemTxMaxGas)); + _maximumBaseFee = uint128(bound(_maximumBaseFee, 1, type(uint128).max)); + _minimumBaseFee = uint32(bound(_minimumBaseFee, 0, _maximumBaseFee - 1)); _gasLimit = uint64(bound(_gasLimit, 21000, _maxResourceLimit)); + _gasLimit = uint64(bound(_gasLimit, 0, gasLimit)); _prevBaseFee = uint128(bound(_prevBaseFee, 0, 3 gwei)); _prevBoughtGas = uint64(bound(_prevBoughtGas, 0, _maxResourceLimit - _gasLimit)); _blockDiff = uint8(bound(_blockDiff, 0, 3)); @@ -1221,11 +1364,16 @@ contract OptimismPortalResourceFuzz_Test is CommonTest { _elasticityMultiplier = uint8(bound(_elasticityMultiplier, 1, type(uint8).max)); // Prevent values that would cause reverts - vm.assume(gasLimit >= _gasLimit); - vm.assume(_minimumBaseFee < _maximumBaseFee); vm.assume(uint256(_maxResourceLimit) + uint256(_systemTxMaxGas) <= gasLimit); vm.assume(((_maxResourceLimit / _elasticityMultiplier) * _elasticityMultiplier) == _maxResourceLimit); + // Although we typically want to limit the usage of vm.assume, we've constructed the above + // bounds to satisfy the assumptions listed in this specific section. These assumptions + // serve only to act as an additional sanity check on top of the bounds and should not + // result in an unnecessary number of test rejections. + vm.assume(gasLimit >= _gasLimit); + vm.assume(_minimumBaseFee < _maximumBaseFee); + // Base fee can increase quickly and mean that we can't buy the amount of gas we want. // Here we add a VM assumption to bound the potential increase. // Compute the maximum possible increase in base fee. @@ -1247,9 +1395,7 @@ contract OptimismPortalResourceFuzz_Test is CommonTest { systemTxMaxGas: _systemTxMaxGas, maximumBaseFee: _maximumBaseFee }); - vm.mockCall( - address(systemConfig), abi.encodeWithSelector(systemConfig.resourceConfig.selector), abi.encode(rcfg) - ); + vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.resourceConfig, ()), abi.encode(rcfg)); // Set the resource params uint256 _prevBlockNum = block.number - _blockDiff; @@ -1307,7 +1453,9 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T token.approve(address(optimismPortal), _mint); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data); @@ -1380,7 +1528,9 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T /// @dev Tests that `depositERC20Transaction` reverts when not enough of the token is approved. function test_depositERC20Transaction_notEnoughAmount_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); vm.expectRevert(stdError.arithmeticError); // Deposit the token into the portal optimismPortal.depositERC20Transaction(address(0), 1, 0, 0, false, ""); @@ -1393,13 +1543,13 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T token.approve(address(optimismPortal), 100); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); - - // Mock the token balance vm.mockCall( - address(token), abi.encodeWithSelector(token.balanceOf.selector, address(optimismPortal)), abi.encode(0) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) ); + // Mock the token balance + vm.mockCall(address(token), abi.encodeCall(token.balanceOf, (address(optimismPortal))), abi.encode(0)); + // Call minimumGasLimit(0) before vm.expectRevert to ensure vm.expectRevert is for depositERC20Transaction uint64 gasLimit = optimismPortal.minimumGasLimit(0); @@ -1412,7 +1562,9 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T /// @dev Tests that `depositERC20Transaction` reverts when creating a contract with a non-zero target. function test_depositERC20Transaction_isCreationNotZeroTarget_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); // Call minimumGasLimit(0) before vm.expectRevert to ensure vm.expectRevert is for depositERC20Transaction uint64 gasLimit = optimismPortal.minimumGasLimit(0); @@ -1425,7 +1577,9 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T /// @dev Tests that `depositERC20Transaction` reverts when the gas limit is too low. function test_depositERC20Transaction_gasLimitTooLow_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); vm.expectRevert(SmallGasLimit.selector); // Deposit the token into the portal @@ -1438,7 +1592,9 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T data[120_000] = 0x01; // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); uint64 gasLimit = optimismPortal.minimumGasLimit(120_001); vm.expectRevert(LargeCalldata.selector); @@ -1453,7 +1609,9 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T token.approve(address(optimismPortal), _amount); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); // Deposit the token into the portal optimismPortal.depositERC20Transaction(address(0), _amount, 0, optimismPortal.minimumGasLimit(0), false, ""); @@ -1463,13 +1621,15 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T } /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. - function test_finalizeWithdrawalTransaction_provenWithdrawalHash_nonEther_succeeds() external { + function test_finalizeWithdrawalTransaction_provenWithdrawalHashNonEther_succeeds() external { // Mint the token to the contract and approve the token for the portal token.mint(address(this), _defaultTx.value); token.approve(address(optimismPortal), _defaultTx.value); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); // Deposit the token into the portal optimismPortal.depositERC20Transaction( @@ -1488,9 +1648,7 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T vm.expectCall(_defaultTx.target, 0, _defaultTx.data); - vm.expectCall( - address(token), 0, abi.encodeWithSelector(token.transfer.selector, _defaultTx.target, _defaultTx.value) - ); + vm.expectCall(address(token), 0, abi.encodeCall(token.transfer, (_defaultTx.target, _defaultTx.value))); optimismPortal.finalizeWithdrawalTransaction(_defaultTx); @@ -1518,7 +1676,9 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T uint64(bound(_gasLimit, optimismPortal.minimumGasLimit(uint64(_data.length)), rcfg.maxResourceLimit)); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); bytes memory opaqueData = abi.encodePacked(uint256(0), _value, _gasLimit, _isCreation, _data); @@ -1539,7 +1699,7 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T } /// @dev Tests that `depositTransaction` succeeds when a custom gas token is used but the msg.value is zero. - function testFuzz_depositTransaction_customGasToken_noValue_senderIsOrigin_succeeds( + function testFuzz_depositTransaction_customGasTokenWithNoValueAndSenderIsOrigin_succeeds( address _to, uint256 _value, uint64 _gasLimit, @@ -1562,7 +1722,7 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T } /// @dev Tests that `depositTransaction` succeeds when a custom gas token is used but the msg.value is zero. - function testFuzz_depositTransaction_customGasToken_noValue_senderNotOrigin_succeeds( + function testFuzz_depositTransaction_customGasTokenWithNoValueAndSenderNotOrigin_succeeds( address _to, uint256 _value, uint64 _gasLimit, @@ -1585,9 +1745,11 @@ contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_T } /// @dev Tests that `depositTransaction` fails when a custom gas token is used and msg.value is non-zero. - function test_depositTransaction_customGasToken_withValue_reverts() external { + function test_depositTransaction_customGasTokenWithValue_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); vm.expectRevert(NoValue.selector); diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 3faa7e3d22611..830323936a606 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -23,18 +23,17 @@ import "src/dispute/lib/Types.sol"; import "src/libraries/PortalErrors.sol"; // Interfaces -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; contract OptimismPortal2_Test is CommonTest { address depositor; function setUp() public virtual override { - super.enableFaultProofs(); super.setUp(); // zero out contracts that should not be used @@ -389,7 +388,7 @@ contract OptimismPortal2_Test is CommonTest { } function test_depositERC20Transaction_balanceOverflow_reverts() external { - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(42), 18)); + vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(42), 18)); // The balance slot vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(type(uint256).max)); @@ -436,7 +435,6 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Use a constructor to set the storage vars above, so as to minimize the number of ffi calls. constructor() { - super.enableFaultProofs(); super.setUp(); _defaultTx = Types.WithdrawalTransaction({ @@ -583,7 +581,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` reverts when the withdrawal has already been proven, and the new /// game has the `CHALLENGER_WINS` status. - function test_proveWithdrawalTransaction_replayProve_differentGameChallengerWins_reverts() external { + function test_proveWithdrawalTransaction_replayProveDifferentGameChallengerWins_reverts() external { vm.expectEmit(address(optimismPortal2)); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(address(optimismPortal2)); @@ -632,7 +630,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against has been /// blacklisted. - function test_proveWithdrawalTransaction_replayProveBlacklisted_suceeds() external { + function test_proveWithdrawalTransaction_replayProveBlacklisted_succeeds() external { vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(true, true, true, true); @@ -670,7 +668,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against has resolved /// against the favor of the root claim. - function test_proveWithdrawalTransaction_replayProveBadProposal_suceeds() external { + function test_proveWithdrawalTransaction_replayProveBadProposal_succeeds() external { vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(true, true, true, true); @@ -704,7 +702,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against is no longer /// of the respected game type. - function test_proveWithdrawalTransaction_replayRespectedGameTypeChanged_suceeds() external { + function test_proveWithdrawalTransaction_replayRespectedGameTypeChanged_succeeds() external { // Prove the withdrawal against a game with the current respected game type. vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); @@ -802,8 +800,171 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { assert(address(bob).balance == bobBalanceBefore + 100); } + /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the target reverts and caller is the + /// ESTIMATION_ADDRESS. + function test_finalizeWithdrawalTransaction_targetFailsAndCallerIsEstimationAddress_reverts() external { + vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. + + vm.prank(alice); + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash, alice, bob); + optimismPortal2.proveWithdrawalTransaction(_defaultTx, _proposedGameIndex, _outputRootProof, _withdrawalProof); + + // Warp and resolve the dispute game. + game.resolveClaim(0, 0); + game.resolve(); + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + + vm.startPrank(alice, Constants.ESTIMATION_ADDRESS); + vm.expectRevert(GasEstimation.selector); + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); + } + + /// @dev Tests that `finalizeWithdrawalTransaction` succeeds when _tx.data is empty. + function test_finalizeWithdrawalTransaction_noTxData_succeeds() external { + Types.WithdrawalTransaction memory _defaultTx_noData = Types.WithdrawalTransaction({ + nonce: 0, + sender: alice, + target: bob, + value: 100, + gasLimit: 100_000, + data: hex"" + }); + // Get withdrawal proof data we can use for testing. + ( + bytes32 _stateRoot_noData, + bytes32 _storageRoot_noData, + bytes32 _outputRoot_noData, + bytes32 _withdrawalHash_noData, + bytes[] memory _withdrawalProof_noData + ) = ffi.getProveWithdrawalTransactionInputs(_defaultTx_noData); + // Setup a dummy output root proof for reuse. + Types.OutputRootProof memory _outputRootProof_noData = Types.OutputRootProof({ + version: bytes32(uint256(0)), + stateRoot: _stateRoot_noData, + messagePasserStorageRoot: _storageRoot_noData, + latestBlockhash: bytes32(uint256(0)) + }); + uint256 _proposedBlockNumber_noData = 0xFF; + IFaultDisputeGame game_noData = IFaultDisputeGame( + payable( + address( + disputeGameFactory.create( + optimismPortal2.respectedGameType(), + Claim.wrap(_outputRoot_noData), + abi.encode(_proposedBlockNumber_noData) + ) + ) + ) + ); + uint256 _proposedGameIndex_noData = disputeGameFactory.gameCount() - 1; + // Warp beyond the chess clocks and finalize the game. + vm.warp(block.timestamp + game_noData.maxClockDuration().raw() + 1 seconds); + // Fund the portal so that we can withdraw ETH. + vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); + vm.deal(address(optimismPortal2), 0xFFFFFFFF); + + uint256 bobBalanceBefore = bob.balance; + + vm.expectEmit(address(optimismPortal2)); + emit WithdrawalProven(_withdrawalHash_noData, alice, bob); + vm.expectEmit(address(optimismPortal2)); + emit WithdrawalProvenExtension1(_withdrawalHash_noData, address(this)); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx_noData, + _disputeGameIndex: _proposedGameIndex_noData, + _outputRootProof: _outputRootProof_noData, + _withdrawalProof: _withdrawalProof_noData + }); + + // Warp and resolve the dispute game. + game_noData.resolveClaim(0, 0); + game_noData.resolve(); + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + + vm.expectEmit(true, true, false, true); + emit WithdrawalFinalized(_withdrawalHash_noData, true); + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx_noData); + + assert(bob.balance == bobBalanceBefore + 100); + } + + /// @dev Tests that `finalizeWithdrawalTransaction` succeeds when _tx.data is empty and with a custom gas token. + function test_finalizeWithdrawalTransaction_noTxDataNonEtherGasToken_succeeds() external { + Types.WithdrawalTransaction memory _defaultTx_noData = Types.WithdrawalTransaction({ + nonce: 0, + sender: alice, + target: bob, + value: 100, + gasLimit: 100_000, + data: hex"" + }); + // Get withdrawal proof data we can use for testing. + ( + bytes32 _stateRoot_noData, + bytes32 _storageRoot_noData, + bytes32 _outputRoot_noData, + bytes32 _withdrawalHash_noData, + bytes[] memory _withdrawalProof_noData + ) = ffi.getProveWithdrawalTransactionInputs(_defaultTx_noData); + // Setup a dummy output root proof for reuse. + Types.OutputRootProof memory _outputRootProof_noData = Types.OutputRootProof({ + version: bytes32(uint256(0)), + stateRoot: _stateRoot_noData, + messagePasserStorageRoot: _storageRoot_noData, + latestBlockhash: bytes32(uint256(0)) + }); + uint256 _proposedBlockNumber_noData = 0xFF; + IFaultDisputeGame game_noData = IFaultDisputeGame( + payable( + address( + disputeGameFactory.create( + optimismPortal2.respectedGameType(), + Claim.wrap(_outputRoot_noData), + abi.encode(_proposedBlockNumber_noData) + ) + ) + ) + ); + uint256 _proposedGameIndex_noData = disputeGameFactory.gameCount() - 1; + // Warp beyond the chess clocks and finalize the game. + vm.warp(block.timestamp + game_noData.maxClockDuration().raw() + 1 seconds); + // Fund the portal so that we can withdraw ETH. + vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); + deal(address(L1Token), address(optimismPortal2), 0xFFFFFFFF); + + // modify the gas token to be non ether + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(L1Token), 18) + ); + + uint256 bobBalanceBefore = L1Token.balanceOf(bob); + + vm.expectEmit(address(optimismPortal2)); + emit WithdrawalProven(_withdrawalHash_noData, alice, bob); + vm.expectEmit(address(optimismPortal2)); + emit WithdrawalProvenExtension1(_withdrawalHash_noData, address(this)); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx_noData, + _disputeGameIndex: _proposedGameIndex_noData, + _outputRootProof: _outputRootProof_noData, + _withdrawalProof: _withdrawalProof_noData + }); + + // Warp and resolve the dispute game. + game_noData.resolveClaim(0, 0); + game_noData.resolve(); + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + + vm.expectEmit(true, true, false, true); + emit WithdrawalFinalized(_withdrawalHash_noData, true); + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx_noData); + + assert(L1Token.balanceOf(bob) == bobBalanceBefore + 100); + } + /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. - function test_finalizeWithdrawalTransaction_provenWithdrawalHash_ether_succeeds() external { + function test_finalizeWithdrawalTransaction_provenWithdrawalHashEther_succeeds() external { uint256 bobBalanceBefore = address(bob).balance; vm.expectEmit(address(optimismPortal2)); @@ -891,10 +1052,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { } /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. - function test_finalizeWithdrawalTransaction_provenWithdrawalHash_nonEther_targetToken_reverts() external { + function test_finalizeWithdrawalTransaction_provenWithdrawalHashNonEtherTargetToken_reverts() external { vm.mockCall( address(systemConfig), - abi.encodeWithSignature("gasPayingToken()"), + abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(_defaultTx.target), 18) ); @@ -976,7 +1137,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); // Mock a createdAt change in the dispute game. - vm.mockCall(address(game), abi.encodeWithSignature("createdAt()"), abi.encode(block.timestamp + 1)); + vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(block.timestamp + 1)); // Attempt to finalize the withdrawal vm.expectRevert("OptimismPortal: withdrawal timestamp less than dispute game creation timestamp"); @@ -1122,7 +1283,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // this contract's callPortalAndExpectRevert() function above. Types.WithdrawalTransaction memory _testTx = _defaultTx; _testTx.target = address(this); - _testTx.data = abi.encodeWithSelector(this.callPortalAndExpectRevert.selector); + _testTx.data = abi.encodeCall(this.callPortalAndExpectRevert, ()); // Get modified proof inputs. ( @@ -1397,7 +1558,6 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { contract OptimismPortal2_Upgradeable_Test is CommonTest { function setUp() public override { - super.enableFaultProofs(); super.setUp(); } @@ -1423,7 +1583,7 @@ contract OptimismPortal2_Upgradeable_Test is CommonTest { // The value passed to the initialize must be larger than the last value // that initialize was called with. IProxy(payable(address(optimismPortal2))).upgradeToAndCall( - address(nextImpl), abi.encodeWithSelector(NextImpl.initialize.selector, 2) + address(nextImpl), abi.encodeCall(NextImpl.initialize, (2)) ); assertEq(IProxy(payable(address(optimismPortal2))).implementation(), address(nextImpl)); @@ -1443,7 +1603,6 @@ contract OptimismPortal2_ResourceFuzz_Test is CommonTest { uint256 constant MAX_GAS_LIMIT = 30_000_000; function setUp() public override { - super.enableFaultProofs(); super.setUp(); } @@ -1466,8 +1625,13 @@ contract OptimismPortal2_ResourceFuzz_Test is CommonTest { uint64 gasLimit = systemConfig.gasLimit(); // Bound resource config + _systemTxMaxGas = uint32(bound(_systemTxMaxGas, 0, gasLimit - 21000)); _maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, MAX_GAS_LIMIT / 8)); + _maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, gasLimit - _systemTxMaxGas)); + _maximumBaseFee = uint128(bound(_maximumBaseFee, 1, type(uint128).max)); + _minimumBaseFee = uint32(bound(_minimumBaseFee, 0, _maximumBaseFee - 1)); _gasLimit = uint64(bound(_gasLimit, 21000, _maxResourceLimit)); + _gasLimit = uint64(bound(_gasLimit, 0, gasLimit)); _prevBaseFee = uint128(bound(_prevBaseFee, 0, 3 gwei)); _prevBoughtGas = uint64(bound(_prevBoughtGas, 0, _maxResourceLimit - _gasLimit)); _blockDiff = uint8(bound(_blockDiff, 0, 3)); @@ -1475,12 +1639,16 @@ contract OptimismPortal2_ResourceFuzz_Test is CommonTest { _elasticityMultiplier = uint8(bound(_elasticityMultiplier, 1, type(uint8).max)); // Prevent values that would cause reverts - vm.assume(gasLimit >= _gasLimit); - vm.assume(_minimumBaseFee < _maximumBaseFee); - vm.assume(_baseFeeMaxChangeDenominator > 1); vm.assume(uint256(_maxResourceLimit) + uint256(_systemTxMaxGas) <= gasLimit); vm.assume(((_maxResourceLimit / _elasticityMultiplier) * _elasticityMultiplier) == _maxResourceLimit); + // Although we typically want to limit the usage of vm.assume, we've constructed the above + // bounds to satisfy the assumptions listed in this specific section. These assumptions + // serve only to act as an additional sanity check on top of the bounds and should not + // result in an unnecessary number of test rejections. + vm.assume(gasLimit >= _gasLimit); + vm.assume(_minimumBaseFee < _maximumBaseFee); + // Base fee can increase quickly and mean that we can't buy the amount of gas we want. // Here we add a VM assumption to bound the potential increase. // Compute the maximum possible increase in base fee. @@ -1502,9 +1670,7 @@ contract OptimismPortal2_ResourceFuzz_Test is CommonTest { systemTxMaxGas: _systemTxMaxGas, maximumBaseFee: _maximumBaseFee }); - vm.mockCall( - address(systemConfig), abi.encodeWithSelector(systemConfig.resourceConfig.selector), abi.encode(rcfg) - ); + vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.resourceConfig, ()), abi.encode(rcfg)); // Set the resource params uint256 _prevBlockNum = block.number - _blockDiff; @@ -1562,7 +1728,9 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal token.approve(address(optimismPortal2), _mint); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data); @@ -1575,7 +1743,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal ); // Deposit the token into the portal - optimismPortal.depositERC20Transaction(_to, _mint, _value, _gasLimit, _isCreation, _data); + optimismPortal2.depositERC20Transaction(_to, _mint, _value, _gasLimit, _isCreation, _data); // Assert final balance equals the deposited amount assertEq(token.balanceOf(address(optimismPortal2)), _mint); @@ -1635,7 +1803,9 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal /// @dev Tests that `depositERC20Transaction` reverts when not enough of the token is approved. function test_depositERC20Transaction_notEnoughAmount_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); vm.expectRevert(stdError.arithmeticError); // Deposit the token into the portal optimismPortal2.depositERC20Transaction(address(0), 1, 0, 0, false, ""); @@ -1648,13 +1818,13 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal token.approve(address(optimismPortal2), 100); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); - - // Mock the token balance vm.mockCall( - address(token), abi.encodeWithSelector(token.balanceOf.selector, address(optimismPortal)), abi.encode(0) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) ); + // Mock the token balance + vm.mockCall(address(token), abi.encodeCall(token.balanceOf, (address(optimismPortal2))), abi.encode(0)); + // Call minimumGasLimit(0) before vm.expectRevert to ensure vm.expectRevert is for depositERC20Transaction uint64 gasLimit = optimismPortal2.minimumGasLimit(0); @@ -1667,7 +1837,9 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal /// @dev Tests that `depositERC20Transaction` reverts when creating a contract with a non-zero target. function test_depositERC20Transaction_isCreationNotZeroTarget_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); // Call minimumGasLimit(0) before vm.expectRevert to ensure vm.expectRevert is for depositERC20Transaction uint64 gasLimit = optimismPortal2.minimumGasLimit(0); @@ -1680,7 +1852,9 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal /// @dev Tests that `depositERC20Transaction` reverts when the gas limit is too low. function test_depositERC20Transaction_gasLimitTooLow_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); vm.expectRevert(SmallGasLimit.selector); // Deposit the token into the portal @@ -1693,7 +1867,9 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal data[120_000] = 0x01; // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); uint64 gasLimit = optimismPortal2.minimumGasLimit(120_001); vm.expectRevert(LargeCalldata.selector); @@ -1708,27 +1884,31 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal token.approve(address(optimismPortal2), _amount); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); // Deposit the token into the portal - optimismPortal2.depositERC20Transaction(address(0), _amount, 0, optimismPortal.minimumGasLimit(0), false, ""); + optimismPortal2.depositERC20Transaction(address(0), _amount, 0, optimismPortal2.minimumGasLimit(0), false, ""); // Check that the balance has been correctly updated assertEq(optimismPortal2.balance(), _amount); } /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. - function test_finalizeWithdrawalTransaction_provenWithdrawalHash_nonEther_succeeds() external { + function test_finalizeWithdrawalTransaction_provenWithdrawalHashWithNonEther_succeeds() external { // Mint the token to the contract and approve the token for the portal token.mint(address(this), _defaultTx.value); token.approve(address(optimismPortal2), _defaultTx.value); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); // Deposit the token into the portal optimismPortal2.depositERC20Transaction( - address(bob), _defaultTx.value, 0, optimismPortal.minimumGasLimit(0), false, "" + address(bob), _defaultTx.value, 0, optimismPortal2.minimumGasLimit(0), false, "" ); assertEq(optimismPortal2.balance(), _defaultTx.value); @@ -1752,9 +1932,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal vm.expectCall(_defaultTx.target, 0, _defaultTx.data); - vm.expectCall( - address(token), 0, abi.encodeWithSelector(token.transfer.selector, _defaultTx.target, _defaultTx.value) - ); + vm.expectCall(address(token), 0, abi.encodeCall(token.transfer, (_defaultTx.target, _defaultTx.value))); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1782,7 +1960,9 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal uint64(bound(_gasLimit, optimismPortal2.minimumGasLimit(uint64(_data.length)), rcfg.maxResourceLimit)); // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); bytes memory opaqueData = abi.encodePacked(uint256(0), _value, _gasLimit, _isCreation, _data); @@ -1803,7 +1983,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal } /// @dev Tests that `depositTransaction` succeeds when a custom gas token is used but the msg.value is zero. - function testFuzz_depositTransaction_customGasToken_noValue_senderIsOrigin_succeeds( + function testFuzz_depositTransaction_customGasTokenWithNoValueAndSenderIsOrigin_succeeds( address _to, uint256 _value, uint64 _gasLimit, @@ -1826,7 +2006,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal } /// @dev Tests that `depositTransaction` succeeds when a custom gas token is used but the msg.value is zero. - function testFuzz_depositTransaction_customGasToken_noValue_senderNotOrigin_succeeds( + function testFuzz_depositTransaction_customGasTokenWithNoValueAndSenderNotOrigin_succeeds( address _to, uint256 _value, uint64 _gasLimit, @@ -1849,9 +2029,11 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal } /// @dev Tests that `depositTransaction` fails when a custom gas token is used and msg.value is non-zero. - function test_depositTransaction_customGasToken_withValue_reverts() external { + function test_depositTransaction_customGasTokenWithValue_reverts() external { // Mock the gas paying token to be the ERC20 token - vm.mockCall(address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(token), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + ); vm.expectRevert(NoValue.selector); diff --git a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol index bc9a980276aa0..0c08cab3e2d1a 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities +// Testing import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries @@ -9,13 +9,9 @@ import { Constants } from "src/libraries/Constants.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import "src/libraries/PortalErrors.sol"; -// Target contract dependencies -import "src/libraries/PortalErrors.sol"; -import { OptimismPortalInterop } from "src/L1/OptimismPortalInterop.sol"; -import { L1BlockInterop, ConfigType } from "src/L2/L1BlockInterop.sol"; - // Interfaces -import { IOptimismPortalInterop } from "src/L1/interfaces/IOptimismPortalInterop.sol"; +import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; +import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; contract OptimismPortalInterop_Test is CommonTest { /// @notice Marked virtual to be overridden in @@ -35,7 +31,7 @@ contract OptimismPortalInterop_Test is CommonTest { _mint: 0, _gasLimit: 200_000, _isCreation: false, - _data: abi.encodeCall(L1BlockInterop.setConfig, (ConfigType.SET_GAS_PAYING_TOKEN, _value)) + _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.SET_GAS_PAYING_TOKEN, _value)) }); vm.prank(address(_optimismPortalInterop().systemConfig())); @@ -43,7 +39,7 @@ contract OptimismPortalInterop_Test is CommonTest { } /// @dev Tests that setting the gas paying token config as not the system config reverts. - function testFuzz_setConfig_gasPayingToken_notSystemConfig_reverts(bytes calldata _value) public { + function testFuzz_setConfig_gasPayingTokenButNotSystemConfig_reverts(bytes calldata _value) public { vm.expectRevert(Unauthorized.selector); _optimismPortalInterop().setConfig(ConfigType.SET_GAS_PAYING_TOKEN, _value); } @@ -58,7 +54,7 @@ contract OptimismPortalInterop_Test is CommonTest { _mint: 0, _gasLimit: 200_000, _isCreation: false, - _data: abi.encodeCall(L1BlockInterop.setConfig, (ConfigType.ADD_DEPENDENCY, _value)) + _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.ADD_DEPENDENCY, _value)) }); vm.prank(address(_optimismPortalInterop().systemConfig())); @@ -66,7 +62,7 @@ contract OptimismPortalInterop_Test is CommonTest { } /// @dev Tests that setting the add dependency config as not the system config reverts. - function testFuzz_setConfig_addDependency_notSystemConfig_reverts(bytes calldata _value) public { + function testFuzz_setConfig_addDependencyButNotSystemConfig_reverts(bytes calldata _value) public { vm.expectRevert(Unauthorized.selector); _optimismPortalInterop().setConfig(ConfigType.ADD_DEPENDENCY, _value); } @@ -81,7 +77,7 @@ contract OptimismPortalInterop_Test is CommonTest { _mint: 0, _gasLimit: 200_000, _isCreation: false, - _data: abi.encodeCall(L1BlockInterop.setConfig, (ConfigType.REMOVE_DEPENDENCY, _value)) + _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.REMOVE_DEPENDENCY, _value)) }); vm.prank(address(_optimismPortalInterop().systemConfig())); @@ -89,7 +85,7 @@ contract OptimismPortalInterop_Test is CommonTest { } /// @dev Tests that setting the remove dependency config as not the system config reverts. - function testFuzz_setConfig_removeDependency_notSystemConfig_reverts(bytes calldata _value) public { + function testFuzz_setConfig_removeDependencyButNotSystemConfig_reverts(bytes calldata _value) public { vm.expectRevert(Unauthorized.selector); _optimismPortalInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, _value); } diff --git a/packages/contracts-bedrock/test/L1/ProtocolVersions.t.sol b/packages/contracts-bedrock/test/L1/ProtocolVersions.t.sol index fc6ea447d231c..28d9ef2b0b83e 100644 --- a/packages/contracts-bedrock/test/L1/ProtocolVersions.t.sol +++ b/packages/contracts-bedrock/test/L1/ProtocolVersions.t.sol @@ -6,8 +6,8 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Interfaces -import { IProxy } from "src/universal/interfaces/IProxy.sol"; -import { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; contract ProtocolVersions_Init is CommonTest { event ConfigUpdate(uint256 indexed version, IProtocolVersions.UpdateType indexed updateType, bytes data); diff --git a/packages/contracts-bedrock/test/L1/ResourceMetering.t.sol b/packages/contracts-bedrock/test/L1/ResourceMetering.t.sol index 6d01cdb308671..d49aa2337bbf0 100644 --- a/packages/contracts-bedrock/test/L1/ResourceMetering.t.sol +++ b/packages/contracts-bedrock/test/L1/ResourceMetering.t.sol @@ -11,7 +11,7 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { Constants } from "src/libraries/Constants.sol"; // Interfaces -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; contract MeterUser is ResourceMetering { ResourceMetering.ResourceConfig public innerConfig; @@ -201,20 +201,21 @@ contract ResourceMetering_Test is Test { function testFuzz_meter_largeBlockDiff_succeeds(uint64 _amount, uint256 _blockDiff) external { // This test fails if the following line is commented out. // At 12 seconds per block, this number is effectively unreachable. - vm.assume(_blockDiff < 433576281058164217753225238677900874458691); + _blockDiff = uint256(bound(_blockDiff, 0, 433576281058164217753225238677900874458690)); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig(); uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier); uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier); - vm.assume(_amount < target * elasticityMultiplier); + _amount = uint64(bound(_amount, 0, target * elasticityMultiplier)); + vm.roll(initialBlockNum + _blockDiff); meter.use(_amount); } function testFuzz_meter_useGas_succeeds(uint64 _amount) external { (, uint64 prevBoughtGas,) = meter.params(); - vm.assume(prevBoughtGas + _amount <= meter.resourceConfig().maxResourceLimit); + _amount = uint64(bound(_amount, 0, meter.resourceConfig().maxResourceLimit - prevBoughtGas)); meter.use(_amount); @@ -374,8 +375,8 @@ contract ArtifactResourceMetering_Test is Test { // Call the metering code and catch the various // types of errors. uint256 gasConsumed = 0; - try meter.use{ gas: 30_000_000 }(requestedGas) returns (uint256 _gasConsumed) { - gasConsumed = _gasConsumed; + try meter.use{ gas: 30_000_000 }(requestedGas) returns (uint256 gasConsumed_) { + gasConsumed = gasConsumed_; } catch (bytes memory err) { bytes32 hash = keccak256(err); if (hash == cannotBuyMoreGas) { diff --git a/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol b/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol index ece5c68254c91..ed51e019acaf2 100644 --- a/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol +++ b/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol @@ -4,11 +4,12 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; // Target contract dependencies -import { Proxy } from "src/universal/Proxy.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; // Target contract -import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract SuperchainConfig_Init_Test is CommonTest { /// @dev Tests that initialization sets the correct values. These are defined in CommonTest.sol. @@ -19,13 +20,23 @@ contract SuperchainConfig_Init_Test is CommonTest { /// @dev Tests that it can be intialized as paused. function test_initialize_paused_succeeds() external { - Proxy newProxy = new Proxy(alice); - ISuperchainConfig newImpl = ISuperchainConfig(address(new SuperchainConfig())); + IProxy newProxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (alice))) + }) + ); + ISuperchainConfig newImpl = ISuperchainConfig( + DeployUtils.create1({ + _name: "SuperchainConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) + }) + ); vm.startPrank(alice); newProxy.upgradeToAndCall( address(newImpl), - abi.encodeWithSelector(ISuperchainConfig.initialize.selector, deploy.cfg().superchainConfigGuardian(), true) + abi.encodeCall(ISuperchainConfig.initialize, (deploy.cfg().superchainConfigGuardian(), true)) ); assertTrue(ISuperchainConfig(address(newProxy)).paused()); diff --git a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol index f2a0af0d0b999..f7cea088bcf46 100644 --- a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol @@ -13,9 +13,9 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { GasPayingToken } from "src/libraries/GasPayingToken.sol"; // Interfaces -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; contract SystemConfig_Init is CommonTest { event ConfigUpdate(uint256 indexed version, ISystemConfig.UpdateType indexed updateType, bytes data); @@ -255,6 +255,19 @@ contract SystemConfig_Init_ResourceConfig is SystemConfig_Init { _initializeWithResourceConfig(config, "SystemConfig: gas limit too low"); } + /// @dev Tests that `setResourceConfig` reverts if the gas limit is too low. + function test_setResourceConfig_elasticityMultiplierIs0_reverts() external { + IResourceMetering.ResourceConfig memory config = IResourceMetering.ResourceConfig({ + maxResourceLimit: 20_000_000, + elasticityMultiplier: 0, + baseFeeMaxChangeDenominator: 8, + systemTxMaxGas: 1_000_000, + minimumBaseFee: 1 gwei, + maximumBaseFee: 2 gwei + }); + _initializeWithResourceConfig(config, "SystemConfig: elasticity multiplier cannot be 0"); + } + /// @dev Tests that `setResourceConfig` reverts if the elasticity multiplier /// and max resource limit are configured such that there is a loss of precision. function test_setResourceConfig_badPrecision_reverts() external { @@ -311,7 +324,7 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init { function setUp() public override { token = new ERC20("Silly", "SIL"); super.enableCustomGasToken(address(token)); - super.enableFaultProofs(); + super.setUp(); } @@ -372,12 +385,21 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init { // don't use multicall3's address vm.assume(_token != MULTICALL3_ADDRESS); - vm.assume(bytes(_name).length <= 32); - vm.assume(bytes(_symbol).length <= 32); + // Using vm.assume() would cause too many test rejections. + string memory name = _name; + if (bytes(_name).length > 32) { + name = _name[:32]; + } - vm.mockCall(_token, abi.encodeWithSelector(token.decimals.selector), abi.encode(18)); - vm.mockCall(_token, abi.encodeWithSelector(token.name.selector), abi.encode(_name)); - vm.mockCall(_token, abi.encodeWithSelector(token.symbol.selector), abi.encode(_symbol)); + // Using vm.assume() would cause too many test rejections. + string memory symbol = _symbol; + if (bytes(_symbol).length > 32) { + symbol = _symbol[:32]; + } + + vm.mockCall(_token, abi.encodeCall(token.decimals, ()), abi.encode(18)); + vm.mockCall(_token, abi.encodeCall(token.name, ()), abi.encode(name)); + vm.mockCall(_token, abi.encodeCall(token.symbol, ()), abi.encode(symbol)); cleanStorageAndInit(_token); @@ -390,13 +412,13 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init { assertEq(systemConfig.gasPayingTokenSymbol(), "ETH"); } else { assertEq(addr, _token); - assertEq(systemConfig.gasPayingTokenName(), _name); - assertEq(systemConfig.gasPayingTokenSymbol(), _symbol); + assertEq(systemConfig.gasPayingTokenName(), name); + assertEq(systemConfig.gasPayingTokenSymbol(), symbol); } } /// @dev Tests that initialization sets the correct values and getters work when token address passed is 0. - function test_initialize_customGasToken_zeroTokenAddress_succeeds() external { + function test_initialize_customGasTokenWithZeroTokenAddress_succeeds() external { cleanStorageAndInit(address(0)); (address addr, uint8 decimals) = systemConfig.gasPayingToken(); @@ -408,7 +430,7 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init { } /// @dev Tests that initialization sets the correct values and getters work when token address is Constants.ETHER - function test_initialize_customGasToken_etherTokenAddress_succeeds() external { + function test_initialize_customGasTokenWithEtherTokenAddress_succeeds() external { cleanStorageAndInit(Constants.ETHER); (address addr, uint8 decimals) = systemConfig.gasPayingToken(); @@ -420,30 +442,30 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init { } /// @dev Tests that initialization fails if decimals are not 18. - function test_initialize_customGasToken_wrongDecimals_fails() external { - vm.mockCall(address(token), abi.encodeWithSelector(token.decimals.selector), abi.encode(8)); + function test_initialize_customGasTokenWrongDecimals_fails() external { + vm.mockCall(address(token), abi.encodeCall(token.decimals, ()), abi.encode(8)); vm.expectRevert("SystemConfig: bad decimals of gas paying token"); cleanStorageAndInit(address(token)); } /// @dev Tests that initialization fails if name is too long. - function test_initialize_customGasToken_nameTooLong_fails() external { + function test_initialize_customGasTokenNameTooLong_fails() external { string memory name = new string(32); name = string.concat(name, "a"); - vm.mockCall(address(token), abi.encodeWithSelector(token.name.selector), abi.encode(name)); + vm.mockCall(address(token), abi.encodeCall(token.name, ()), abi.encode(name)); vm.expectRevert("GasPayingToken: string cannot be greater than 32 bytes"); cleanStorageAndInit(address(token)); } /// @dev Tests that initialization fails if symbol is too long. - function test_initialize_customGasToken_symbolTooLong_fails() external { + function test_initialize_customGasTokenSymbolTooLong_fails() external { string memory symbol = new string(33); symbol = string.concat(symbol, "a"); - vm.mockCall(address(token), abi.encodeWithSelector(token.symbol.selector), abi.encode(symbol)); + vm.mockCall(address(token), abi.encodeCall(token.symbol, ()), abi.encode(symbol)); vm.expectRevert("GasPayingToken: string cannot be greater than 32 bytes"); cleanStorageAndInit(address(token)); @@ -526,6 +548,27 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init { vm.expectRevert("SystemConfig: gas limit too high"); systemConfig.setGasLimit(maximumGasLimit + 1); } + + /// @dev Tests that `setEIP1559Params` reverts if the caller is not the owner. + function test_setEIP1559Params_notOwner_reverts(uint32 _denominator, uint32 _elasticity) external { + vm.expectRevert("Ownable: caller is not the owner"); + systemConfig.setEIP1559Params({ _denominator: _denominator, _elasticity: _elasticity }); + } + + /// @dev Tests that `setEIP1559Params` reverts if the denominator is zero. + function test_setEIP1559Params_zeroDenominator_reverts(uint32 _elasticity) external { + vm.prank(systemConfig.owner()); + vm.expectRevert("SystemConfig: denominator must be >= 1"); + systemConfig.setEIP1559Params({ _denominator: 0, _elasticity: _elasticity }); + } + + /// @dev Tests that `setEIP1559Params` reverts if the elasticity is zero. + function test_setEIP1559Params_zeroElasticity_reverts(uint32 _denominator) external { + _denominator = uint32(bound(_denominator, 1, type(uint32).max)); + vm.prank(systemConfig.owner()); + vm.expectRevert("SystemConfig: elasticity must be >= 1"); + systemConfig.setEIP1559Params({ _denominator: _denominator, _elasticity: 0 }); + } } contract SystemConfig_Setters_Test is SystemConfig_Init { @@ -544,7 +587,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { // always zero out most significant byte newScalar = (newScalar << 16) >> 16; vm.expectEmit(address(systemConfig)); - emit ConfigUpdate(0, ISystemConfig.UpdateType.GAS_CONFIG, abi.encode(newOverhead, newScalar)); + emit ConfigUpdate(0, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(newOverhead, newScalar)); vm.prank(systemConfig.owner()); systemConfig.setGasConfig(newOverhead, newScalar); @@ -557,7 +600,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { ffi.encodeScalarEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); vm.expectEmit(address(systemConfig)); - emit ConfigUpdate(0, ISystemConfig.UpdateType.GAS_CONFIG, abi.encode(systemConfig.overhead(), encoded)); + emit ConfigUpdate(0, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(systemConfig.overhead(), encoded)); vm.prank(systemConfig.owner()); systemConfig.setGasConfigEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); @@ -593,4 +636,20 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { systemConfig.setUnsafeBlockSigner(newUnsafeSigner); assertEq(systemConfig.unsafeBlockSigner(), newUnsafeSigner); } + + /// @dev Tests that `setEIP1559Params` updates the EIP1559 parameters successfully. + function testFuzz_setEIP1559Params_succeeds(uint32 _denominator, uint32 _elasticity) external { + vm.assume(_denominator > 1); + vm.assume(_elasticity > 1); + + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate( + 0, ISystemConfig.UpdateType.EIP_1559_PARAMS, abi.encode(uint256(_denominator) << 32 | uint64(_elasticity)) + ); + + vm.prank(systemConfig.owner()); + systemConfig.setEIP1559Params(_denominator, _elasticity); + assertEq(systemConfig.eip1559Denominator(), _denominator); + assertEq(systemConfig.eip1559Elasticity(), _elasticity); + } } diff --git a/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol b/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol index 0e47529c760ca..8fd6daeed8460 100644 --- a/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol +++ b/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol @@ -6,7 +6,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { ConfigType } from "src/L2/L1BlockInterop.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; @@ -14,9 +13,10 @@ import { StaticConfig } from "src/libraries/StaticConfig.sol"; import { GasPayingToken } from "src/libraries/GasPayingToken.sol"; // Interfaces -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISystemConfigInterop } from "src/L1/interfaces/ISystemConfigInterop.sol"; -import { IOptimismPortalInterop } from "src/L1/interfaces/IOptimismPortalInterop.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; +import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; +import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; contract SystemConfigInterop_Test is CommonTest { /// @notice Marked virtual to be overridden in @@ -26,6 +26,19 @@ contract SystemConfigInterop_Test is CommonTest { super.setUp(); } + /// @dev Tests that when the decimals is not 18, initialization reverts. + function test_initialize_decimalsIsNot18_reverts(uint8 decimals) external { + vm.assume(decimals != 18); + address _token = address(L1Token); + + vm.mockCall(_token, abi.encodeCall(ERC20.name, ()), abi.encode("Token")); + vm.mockCall(_token, abi.encodeCall(ERC20.symbol, ()), abi.encode("TKN")); + vm.mockCall(_token, abi.encodeCall(ERC20.decimals, ()), abi.encode(decimals)); + + vm.expectRevert("SystemConfig: bad decimals of gas paying token"); + _cleanStorageAndInit(_token); + } + /// @dev Tests that the gas paying token can be set. function testFuzz_setGasPayingToken_succeeds( address _token, @@ -38,12 +51,21 @@ contract SystemConfigInterop_Test is CommonTest { vm.assume(_token != address(0)); vm.assume(_token != Constants.ETHER); - vm.assume(bytes(_name).length <= 32); - vm.assume(bytes(_symbol).length <= 32); + // Using vm.assume() would cause too many test rejections. + string memory name = _name; + if (bytes(_name).length > 32) { + name = _name[:32]; + } + + // Using vm.assume() would cause too many test rejections. + string memory symbol = _symbol; + if (bytes(_symbol).length > 32) { + symbol = _symbol[:32]; + } - vm.mockCall(_token, abi.encodeWithSelector(ERC20.decimals.selector), abi.encode(18)); - vm.mockCall(_token, abi.encodeWithSelector(ERC20.name.selector), abi.encode(_name)); - vm.mockCall(_token, abi.encodeWithSelector(ERC20.symbol.selector), abi.encode(_symbol)); + vm.mockCall(_token, abi.encodeCall(ERC20.decimals, ()), abi.encode(18)); + vm.mockCall(_token, abi.encodeCall(ERC20.name, ()), abi.encode(name)); + vm.mockCall(_token, abi.encodeCall(ERC20.symbol, ()), abi.encode(symbol)); vm.expectCall( address(optimismPortal), @@ -54,8 +76,8 @@ contract SystemConfigInterop_Test is CommonTest { StaticConfig.encodeSetGasPayingToken({ _token: _token, _decimals: 18, - _name: GasPayingToken.sanitize(_name), - _symbol: GasPayingToken.sanitize(_symbol) + _name: GasPayingToken.sanitize(name), + _symbol: GasPayingToken.sanitize(symbol) }) ) ) @@ -80,7 +102,9 @@ contract SystemConfigInterop_Test is CommonTest { /// @dev Tests that adding a dependency as not the dependency manager reverts. function testFuzz_addDependency_notDependencyManager_reverts(uint256 _chainId) public { + require(alice != _systemConfigInterop().dependencyManager(), "SystemConfigInterop_Test: 100"); vm.expectRevert("SystemConfig: caller is not the dependency manager"); + vm.prank(alice); _systemConfigInterop().addDependency(_chainId); } @@ -100,7 +124,9 @@ contract SystemConfigInterop_Test is CommonTest { /// @dev Tests that removing a dependency as not the dependency manager reverts. function testFuzz_removeDependency_notDependencyManager_reverts(uint256 _chainId) public { + require(alice != _systemConfigInterop().dependencyManager(), "SystemConfigInterop_Test: 100"); vm.expectRevert("SystemConfig: caller is not the dependency manager"); + vm.prank(alice); _systemConfigInterop().removeDependency(_chainId); } diff --git a/packages/contracts-bedrock/test/L2/BaseFeeVault.t.sol b/packages/contracts-bedrock/test/L2/BaseFeeVault.t.sol index bf63a700c0985..1864e1d076640 100644 --- a/packages/contracts-bedrock/test/L2/BaseFeeVault.t.sol +++ b/packages/contracts-bedrock/test/L2/BaseFeeVault.t.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.15; // Testing utilities -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Types } from "src/libraries/Types.sol"; // Test the implementations of the FeeVault -contract FeeVault_Test is Bridge_Initializer { +contract FeeVault_Test is CommonTest { /// @dev Tests that the constructor sets the correct values. function test_constructor_baseFeeVault_succeeds() external view { assertEq(baseFeeVault.RECIPIENT(), deploy.cfg().baseFeeVaultRecipient()); diff --git a/packages/contracts-bedrock/test/L2/CrossDomainOwnable.t.sol b/packages/contracts-bedrock/test/L2/CrossDomainOwnable.t.sol index 1e7c10556a536..c66bd1f94ab3d 100644 --- a/packages/contracts-bedrock/test/L2/CrossDomainOwnable.t.sol +++ b/packages/contracts-bedrock/test/L2/CrossDomainOwnable.t.sol @@ -68,7 +68,7 @@ contract CrossDomainOwnableThroughPortal_Test is CommonTest { _value: 0, _gasLimit: 30_000, _isCreation: false, - _data: abi.encodeWithSelector(XDomainSetter.set.selector, 1) + _data: abi.encodeCall(XDomainSetter.set, (1)) }); // Simulate the operation of the `op-node` by parsing data diff --git a/packages/contracts-bedrock/test/L2/CrossDomainOwnable2.t.sol b/packages/contracts-bedrock/test/L2/CrossDomainOwnable2.t.sol index fa1244a0b0e12..7bdbdaa9f7089 100644 --- a/packages/contracts-bedrock/test/L2/CrossDomainOwnable2.t.sol +++ b/packages/contracts-bedrock/test/L2/CrossDomainOwnable2.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; // Testing utilities -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Hashing } from "src/libraries/Hashing.sol"; @@ -23,7 +23,7 @@ contract XDomainSetter2 is CrossDomainOwnable2 { } } -contract CrossDomainOwnable2_Test is Bridge_Initializer { +contract CrossDomainOwnable2_Test is CommonTest { XDomainSetter2 setter; /// @dev Sets up the test suite. @@ -58,7 +58,7 @@ contract CrossDomainOwnable2_Test is Bridge_Initializer { address target = address(setter); uint256 value = 0; uint256 minGasLimit = 0; - bytes memory message = abi.encodeWithSelector(XDomainSetter2.set.selector, 1); + bytes memory message = abi.encodeCall(XDomainSetter2.set, (1)); bytes32 hash = Hashing.hashCrossDomainMessage( Encoding.encodeVersionedNonce(nonce, 1), sender, target, value, minGasLimit, message @@ -85,12 +85,7 @@ contract CrossDomainOwnable2_Test is Bridge_Initializer { // the L1CrossDomainMessenger vm.prank(AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger))); l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce(1, 1), - owner, - address(setter), - 0, - 0, - abi.encodeWithSelector(XDomainSetter2.set.selector, 2) + Encoding.encodeVersionedNonce(1, 1), owner, address(setter), 0, 0, abi.encodeCall(XDomainSetter2.set, (2)) ); assertEq(setter.value(), 2); diff --git a/packages/contracts-bedrock/test/L2/CrossDomainOwnable3.t.sol b/packages/contracts-bedrock/test/L2/CrossDomainOwnable3.t.sol index 432007ecd6d7d..e1bac7c784bb9 100644 --- a/packages/contracts-bedrock/test/L2/CrossDomainOwnable3.t.sol +++ b/packages/contracts-bedrock/test/L2/CrossDomainOwnable3.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; // Testing utilities -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Hashing } from "src/libraries/Hashing.sol"; @@ -23,7 +23,7 @@ contract XDomainSetter3 is CrossDomainOwnable3 { } } -contract CrossDomainOwnable3_Test is Bridge_Initializer { +contract CrossDomainOwnable3_Test is CommonTest { XDomainSetter3 setter; /// @dev CrossDomainOwnable3.sol transferOwnership event @@ -101,7 +101,7 @@ contract CrossDomainOwnable3_Test is Bridge_Initializer { address target = address(setter); uint256 value = 0; uint256 minGasLimit = 0; - bytes memory message = abi.encodeWithSelector(XDomainSetter3.set.selector, 1); + bytes memory message = abi.encodeCall(XDomainSetter3.set, (1)); bytes32 hash = Hashing.hashCrossDomainMessage( Encoding.encodeVersionedNonce(nonce, 1), sender, target, value, minGasLimit, message @@ -216,12 +216,7 @@ contract CrossDomainOwnable3_Test is Bridge_Initializer { // the L1CrossDomainMessenger vm.prank(AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger))); l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce(1, 1), - bob, - address(setter), - 0, - 0, - abi.encodeWithSelector(XDomainSetter3.set.selector, 2) + Encoding.encodeVersionedNonce(1, 1), bob, address(setter), 0, 0, abi.encodeCall(XDomainSetter3.set, (2)) ); assertEq(setter.value(), 2); diff --git a/packages/contracts-bedrock/test/L2/CrossL2Inbox.t.sol b/packages/contracts-bedrock/test/L2/CrossL2Inbox.t.sol index 8078e2c01c74e..100019034df67 100644 --- a/packages/contracts-bedrock/test/L2/CrossL2Inbox.t.sol +++ b/packages/contracts-bedrock/test/L2/CrossL2Inbox.t.sol @@ -11,6 +11,7 @@ import { TransientContext } from "src/libraries/TransientContext.sol"; // Target contracts import { CrossL2Inbox, + Identifier, NotEntered, NoExecutingDeposits, InvalidTimestamp, @@ -19,8 +20,7 @@ import { NotDepositor, InteropStartAlreadySet } from "src/L2/CrossL2Inbox.sol"; -import { IL1BlockInterop } from "src/L2/interfaces/IL1BlockInterop.sol"; -import { ICrossL2Inbox } from "src/L2/interfaces/ICrossL2Inbox.sol"; +import { IL1BlockInterop } from "interfaces/L2/IL1BlockInterop.sol"; /// @title CrossL2InboxWithModifiableTransientStorage /// @dev CrossL2Inbox contract with methods to modify the transient storage. @@ -141,7 +141,7 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `executeMessage` function succeeds. function testFuzz_executeMessage_succeeds( - ICrossL2Inbox.Identifier memory _id, + Identifier memory _id, address _target, bytes calldata _message, uint256 _value @@ -160,7 +160,7 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); @@ -170,7 +170,7 @@ contract CrossL2InboxTest is Test { // Ensure that the chain ID is in the dependency set vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId), + data: abi.encodeCall(IL1BlockInterop.isInDependencySet, (_id.chainId)), returnData: abi.encode(true) }); @@ -200,14 +200,14 @@ contract CrossL2InboxTest is Test { /// @dev Mock reentrant function that calls the `executeMessage` function. /// @param _id Identifier to pass to the `executeMessage` function. - function mockReentrant(ICrossL2Inbox.Identifier calldata _id) external payable { + function mockReentrant(Identifier calldata _id) external payable { crossL2Inbox.executeMessage({ _id: _id, _target: address(0), _message: "" }); } /// @dev Tests that the `executeMessage` function successfully handles reentrant calls. function testFuzz_executeMessage_reentrant_succeeds( - ICrossL2Inbox.Identifier memory _id1, // identifier passed to `executeMessage` by the initial call. - ICrossL2Inbox.Identifier memory _id2, // identifier passed to `executeMessage` by the reentrant call. + Identifier memory _id1, // identifier passed to `executeMessage` by the initial call. + Identifier memory _id2, // identifier passed to `executeMessage` by the reentrant call. uint256 _value ) external @@ -222,27 +222,27 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); // Ensure that id1's chain ID is in the dependency set vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id1.chainId), + data: abi.encodeCall(IL1BlockInterop.isInDependencySet, (_id1.chainId)), returnData: abi.encode(true) }); // Ensure that id2's chain ID is in the dependency set vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id2.chainId), + data: abi.encodeCall(IL1BlockInterop.isInDependencySet, (_id2.chainId)), returnData: abi.encode(true) }); // Set the target and message for the reentrant call address target = address(this); - bytes memory message = abi.encodeWithSelector(this.mockReentrant.selector, _id2); + bytes memory message = abi.encodeCall(this.mockReentrant, (_id2)); // Ensure that the contract has enough balance to send with value vm.deal(address(this), _value); @@ -272,7 +272,7 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `executeMessage` function reverts if the transaction comes from a deposit. function testFuzz_executeMessage_isDeposit_reverts( - ICrossL2Inbox.Identifier calldata _id, + Identifier calldata _id, address _target, bytes calldata _message, uint256 _value @@ -282,7 +282,7 @@ contract CrossL2InboxTest is Test { // Ensure it is a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(true) }); @@ -298,7 +298,7 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `executeMessage` function reverts when called with an identifier with an invalid timestamp. function testFuzz_executeMessage_invalidTimestamp_reverts( - ICrossL2Inbox.Identifier calldata _id, + Identifier calldata _id, address _target, bytes calldata _message, uint256 _value @@ -312,7 +312,7 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); @@ -328,8 +328,8 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `executeMessage` function reverts when called with an identifier with a timestamp earlier /// than INTEROP_START timestamp - function testFuzz_executeMessage_invalidTimestamp_interopStart_reverts( - ICrossL2Inbox.Identifier memory _id, + function testFuzz_executeMessage_invalidTimestampInteropStart_reverts( + Identifier memory _id, address _target, bytes calldata _message, uint256 _value @@ -346,7 +346,7 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); @@ -360,7 +360,7 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `executeMessage` function reverts when called with an identifier with a chain ID not in /// dependency set. function testFuzz_executeMessage_invalidChainId_reverts( - ICrossL2Inbox.Identifier memory _id, + Identifier memory _id, address _target, bytes calldata _message, uint256 _value @@ -375,14 +375,14 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); // Ensure that the chain ID is NOT in the dependency set vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId), + data: abi.encodeCall(IL1BlockInterop.isInDependencySet, (_id.chainId)), returnData: abi.encode(false) }); @@ -398,7 +398,7 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `executeMessage` function reverts when the target call fails. function testFuzz_executeMessage_targetCallFailed_reverts( - ICrossL2Inbox.Identifier memory _id, + Identifier memory _id, address _target, bytes calldata _message, uint256 _value @@ -419,14 +419,14 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); // Ensure that the chain ID is in the dependency set vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId), + data: abi.encodeCall(IL1BlockInterop.isInDependencySet, (_id.chainId)), returnData: abi.encode(true) }); @@ -443,13 +443,7 @@ contract CrossL2InboxTest is Test { crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message }); } - function testFuzz_validateMessage_succeeds( - ICrossL2Inbox.Identifier memory _id, - bytes32 _messageHash - ) - external - setInteropStart - { + function testFuzz_validateMessage_succeeds(Identifier memory _id, bytes32 _messageHash) external setInteropStart { // Ensure that the id's timestamp is valid (less than or equal to the current block timestamp and greater than // interop start time) _id.timestamp = bound(_id.timestamp, interopStartTime + 1, block.timestamp); @@ -457,14 +451,14 @@ contract CrossL2InboxTest is Test { // Ensure that the chain ID is in the dependency set vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId), + data: abi.encodeCall(IL1BlockInterop.isInDependencySet, (_id.chainId)), returnData: abi.encode(true) }); // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); @@ -476,16 +470,11 @@ contract CrossL2InboxTest is Test { crossL2Inbox.validateMessage(_id, _messageHash); } - function testFuzz_validateMessage_isDeposit_reverts( - ICrossL2Inbox.Identifier calldata _id, - bytes32 _messageHash - ) - external - { + function testFuzz_validateMessage_isDeposit_reverts(Identifier calldata _id, bytes32 _messageHash) external { // Ensure it is a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(true) }); @@ -499,7 +488,7 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `validateMessage` function reverts when called with an identifier with a timestamp later /// than current block.timestamp. function testFuzz_validateMessage_invalidTimestamp_reverts( - ICrossL2Inbox.Identifier calldata _id, + Identifier calldata _id, bytes32 _messageHash ) external @@ -508,7 +497,7 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); @@ -524,8 +513,8 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `validateMessage` function reverts when called with an identifier with a timestamp earlier /// than INTEROP_START timestamp - function testFuzz_validateMessage_invalidTimestamp_interopStart_reverts( - ICrossL2Inbox.Identifier memory _id, + function testFuzz_validateMessage_invalidTimestampInteropStart_reverts( + Identifier memory _id, bytes32 _messageHash ) external @@ -537,7 +526,7 @@ contract CrossL2InboxTest is Test { // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); @@ -551,7 +540,7 @@ contract CrossL2InboxTest is Test { /// @dev Tests that the `validateMessage` function reverts when called with an identifier with a chain ID not in the /// dependency set. function testFuzz_validateMessage_invalidChainId_reverts( - ICrossL2Inbox.Identifier memory _id, + Identifier memory _id, bytes32 _messageHash ) external @@ -564,14 +553,14 @@ contract CrossL2InboxTest is Test { // Ensure that the chain ID is NOT in the dependency set. vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId), + data: abi.encodeCall(IL1BlockInterop.isInDependencySet, (_id.chainId)), returnData: abi.encode(false) }); // Ensure is not a deposit transaction vm.mockCall({ callee: Predeploys.L1_BLOCK_ATTRIBUTES, - data: abi.encodeWithSelector(IL1BlockInterop.isDeposit.selector), + data: abi.encodeCall(IL1BlockInterop.isDeposit, ()), returnData: abi.encode(false) }); diff --git a/packages/contracts-bedrock/test/L2/ETHLiquidity.t.sol b/packages/contracts-bedrock/test/L2/ETHLiquidity.t.sol index d9d2d92a89281..9814e776b2e5e 100644 --- a/packages/contracts-bedrock/test/L2/ETHLiquidity.t.sol +++ b/packages/contracts-bedrock/test/L2/ETHLiquidity.t.sol @@ -105,7 +105,7 @@ contract ETHLiquidity_Test is CommonTest { uint256 amount = STARTING_LIQUIDITY_BALANCE + 1; // Act - vm.expectRevert(); + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args ethLiquidity.mint(amount); // Assert diff --git a/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol b/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol index f81d96b040f46..f013325ef7e7e 100644 --- a/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol +++ b/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol @@ -93,6 +93,7 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { /// @dev Tests that `setGasPrice` reverts since it was removed in bedrock. function test_setGasPrice_doesNotExist_reverts() external { + // nosemgrep: sol-style-use-abi-encodecall (bool success, bytes memory returndata) = address(gasPriceOracle).call(abi.encodeWithSignature("setGasPrice(uint256)", 1)); @@ -102,6 +103,7 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { /// @dev Tests that `setL1BaseFee` reverts since it was removed in bedrock. function test_setL1BaseFee_doesNotExist_reverts() external { + // nosemgrep: sol-style-use-abi-encodecall (bool success, bytes memory returndata) = address(gasPriceOracle).call(abi.encodeWithSignature("setL1BaseFee(uint256)", 1)); @@ -115,6 +117,26 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { vm.expectRevert("GasPriceOracle: Fjord can only be activated after Ecotone"); gasPriceOracle.setFjord(); } + + /// @dev Tests that `getL1Fee` returns the expected value when both fjord and ecotone are not active + function test_getL1Fee_whenFjordAndEcotoneNotActive_succeeds() external { + vm.store(address(gasPriceOracle), bytes32(uint256(0)), bytes32(0)); + bytes memory data = hex"1111"; + + uint256 price = gasPriceOracle.getL1Fee(data); + assertEq(price, 28_600); // ((((16 * data.length(i.e 2)) * (68 * 16)) + l1FeeOverhead(i.e. 310)) * + // l1BaseFee(i.e. 2M) * + // l1FeeScalar(i.e. 10)) / 1e6 + } + + /// @dev Tests that `getL1GasUsed` returns the expected value when both fjord and ecotone are not active + function test_getL1GasUsed_whenFjordAndEcotoneNotActive_succeeds() external { + vm.store(address(gasPriceOracle), bytes32(uint256(0)), bytes32(0)); + bytes memory data = hex"1111"; + + uint256 gas = gasPriceOracle.getL1GasUsed(data); + assertEq(gas, 1_430); // 1398 + (16 * data.length(i.e 2)) + } } contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { @@ -131,7 +153,7 @@ contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { // Execute the function call vm.prank(depositor); (bool success,) = address(l1Block).call(calldataPacked); - require(success, "Function call failed"); + require(success, "GasPriceOracleEcotone_Test: Function call failed"); } /// @dev Tests that `setEcotone` is only callable by the depositor. @@ -222,7 +244,7 @@ contract GasPriceOracleFjordActive_Test is GasPriceOracle_Test { vm.prank(depositor); (bool success,) = address(l1Block).call(calldataPacked); - require(success, "Function call failed"); + require(success, "GasPriceOracleFjordActive_Test: Function call failed"); } /// @dev Tests that `setFjord` cannot be called when Fjord is already activate diff --git a/packages/contracts-bedrock/test/L2/L1Block.t.sol b/packages/contracts-bedrock/test/L2/L1Block.t.sol index 762553a2ff2f3..63e562484bf24 100644 --- a/packages/contracts-bedrock/test/L2/L1Block.t.sol +++ b/packages/contracts-bedrock/test/L2/L1Block.t.sol @@ -163,37 +163,102 @@ contract L1BlockEcotone_Test is L1BlockTest { bytes memory expReturn = hex"3cc50b45"; assertEq(data, expReturn); } + + /// @dev Tests that `blockHash` works for block range [n-256, n) where n is the latest + /// L1 block number known by the L2 system. + function testFuzz_blockHash( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash + ) + external + { + if (number > type(uint64).max - uint64(l1Block.historySize()) - 1) { + number = type(uint64).max - uint64(l1Block.historySize()) - 1; + } + if (uint256(hash) > type(uint256).max - l1Block.historySize() - 1) { + hash = bytes32(type(uint256).max - l1Block.historySize() - 1); + } + + for (uint256 i = 1; i <= l1Block.historySize() + 1; i++) { + bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesEcotone( + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number + uint64(i), + baseFee, + blobBaseFee, + bytes32(uint256(hash) + i), + batcherHash + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(functionCallDataPacked); + assertTrue(success, "function call failed"); + + assertEq(l1Block.number(), number + uint64(i)); + assertEq(l1Block.hash(), bytes32(uint256(hash) + i)); + } + + assertTrue( + l1Block.blockHash(number + l1Block.historySize() + 1) == bytes32(0), + "should return bytes32(0) for the latest L1 block" + ); + assertTrue(l1Block.blockHash(number + 1) == bytes32(0), "should return bytes32(0) for blocks out of range"); + for (uint256 i = 2; i <= l1Block.historySize(); i++) { + assertTrue( + l1Block.blockHash(number + i) == bytes32(uint256(hash) + i), + "blockHash's return value should match the value set" + ); + } + } } contract L1BlockCustomGasToken_Test is L1BlockTest { function testFuzz_setGasPayingToken_succeeds( address _token, uint8 _decimals, - string memory _name, - string memory _symbol + string calldata _name, + string calldata _symbol ) external { vm.assume(_token != address(0)); vm.assume(_token != Constants.ETHER); - vm.assume(bytes(_name).length <= 32); - vm.assume(bytes(_symbol).length <= 32); - bytes32 name = bytes32(abi.encodePacked(_name)); - bytes32 symbol = bytes32(abi.encodePacked(_symbol)); + // Using vm.assume() would cause too many test rejections. + string memory name = _name; + if (bytes(_name).length > 32) { + name = _name[:32]; + } + bytes32 b32name = bytes32(abi.encodePacked(name)); + + // Using vm.assume() would cause too many test rejections. + string memory symbol = _symbol; + if (bytes(_symbol).length > 32) { + symbol = _symbol[:32]; + } + bytes32 b32symbol = bytes32(abi.encodePacked(symbol)); vm.expectEmit(address(l1Block)); - emit GasPayingTokenSet({ token: _token, decimals: _decimals, name: name, symbol: symbol }); + emit GasPayingTokenSet({ token: _token, decimals: _decimals, name: b32name, symbol: b32symbol }); vm.prank(depositor); - l1Block.setGasPayingToken({ _token: _token, _decimals: _decimals, _name: name, _symbol: symbol }); + l1Block.setGasPayingToken({ _token: _token, _decimals: _decimals, _name: b32name, _symbol: b32symbol }); (address token, uint8 decimals) = l1Block.gasPayingToken(); assertEq(token, _token); assertEq(decimals, _decimals); - assertEq(_name, l1Block.gasPayingTokenName()); - assertEq(_symbol, l1Block.gasPayingTokenSymbol()); + assertEq(name, l1Block.gasPayingTokenName()); + assertEq(symbol, l1Block.gasPayingTokenSymbol()); assertTrue(l1Block.isCustomGasToken()); } diff --git a/packages/contracts-bedrock/test/L2/L1BlockInterop.t.sol b/packages/contracts-bedrock/test/L2/L1BlockInterop.t.sol index 6f0ef2188b8c8..a5e086c86d589 100644 --- a/packages/contracts-bedrock/test/L2/L1BlockInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L1BlockInterop.t.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities +// Testing import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { StaticConfig } from "src/libraries/StaticConfig.sol"; - -// Target contract dependencies -import { L1BlockInterop, ConfigType } from "src/L2/L1BlockInterop.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import "src/libraries/L1BlockErrors.sol"; +// Interfaces +import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; + contract L1BlockInteropTest is CommonTest { event GasPayingTokenSet(address indexed token, uint8 indexed decimals, bytes32 name, bytes32 symbol); event DependencyAdded(uint256 indexed chainId); @@ -104,7 +104,7 @@ contract L1BlockInteropTest is CommonTest { } /// @dev Tests that setting the gas paying token config as not the depositor reverts. - function testFuzz_setConfig_gasPayingToken_notDepositor_reverts( + function testFuzz_setConfig_gasPayingTokenButNotDepositor_reverts( address _token, uint8 _decimals, bytes32 _name, @@ -132,13 +132,13 @@ contract L1BlockInteropTest is CommonTest { } /// @dev Tests that adding a dependency reverts if it's the chain's chain id - function test_setConfig_addDependency_chainChainId_reverts() public prankDepositor { + function test_setConfig_addDependencyButChainChainId_reverts() public prankDepositor { vm.expectRevert(AlreadyDependency.selector); _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(block.chainid)); } /// @dev Tests that adding a dependency already in the set reverts - function test_setConfig_addDependency_alreadyDependency_reverts(uint256 _chainId) public prankDepositor { + function test_setConfig_addDependencyButAlreadyDependency_reverts(uint256 _chainId) public prankDepositor { vm.assume(_chainId != block.chainid); _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId)); @@ -148,13 +148,13 @@ contract L1BlockInteropTest is CommonTest { } /// @dev Tests that setting the add dependency config as not the depositor reverts. - function testFuzz_setConfig_addDependency_notDepositor_reverts(uint256 _chainId) public { + function testFuzz_setConfig_addDependencyButNotDepositor_reverts(uint256 _chainId) public { vm.expectRevert(NotDepositor.selector); _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId)); } /// @dev Tests that setting the add dependency config when the dependency set size is too large reverts. - function test_setConfig_addDependency_dependencySetSizeTooLarge_reverts() public prankDepositor { + function test_setConfig_addDependencyButDependencySetSizeTooLarge_reverts() public prankDepositor { for (uint256 i = 0; i < type(uint8).max; i++) { _l1BlockInterop().setConfig(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(i)); } @@ -179,19 +179,19 @@ contract L1BlockInteropTest is CommonTest { } /// @dev Tests that setting the remove dependency config as not the depositor reverts. - function testFuzz_setConfig_removeDependency_notDepositor_reverts(uint256 _chainId) public { + function testFuzz_setConfig_removeDependencyButNotDepositor_reverts(uint256 _chainId) public { vm.expectRevert(NotDepositor.selector); _l1BlockInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(_chainId)); } /// @dev Tests that setting the remove dependency config for the chain's chain ID reverts. - function test_setConfig_removeDependency_chainChainId_reverts() public prankDepositor { + function test_setConfig_removeDependencyButChainChainId_reverts() public prankDepositor { vm.expectRevert(CantRemovedDependency.selector); _l1BlockInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(block.chainid)); } /// @dev Tests that setting the remove dependency config for a chain ID that is not in the dependency set reverts. - function testFuzz_setConfig_removeDependency_notDependency_reverts(uint256 _chainId) public prankDepositor { + function testFuzz_setConfig_removeDependencyButNotDependency_reverts(uint256 _chainId) public prankDepositor { vm.assume(_chainId != block.chainid); vm.expectRevert(NotDependency.selector); @@ -199,8 +199,8 @@ contract L1BlockInteropTest is CommonTest { } /// @dev Returns the L1BlockInterop instance. - function _l1BlockInterop() internal view returns (L1BlockInterop) { - return L1BlockInterop(address(l1Block)); + function _l1BlockInterop() internal view returns (IL1BlockInterop) { + return IL1BlockInterop(address(l1Block)); } } @@ -261,7 +261,7 @@ contract L1BlockInteropSetL1BlockValuesInterop_Test is L1BlockInteropTest { vm.prank(_l1BlockInterop().DEPOSITOR_ACCOUNT()); (bool success,) = address(l1Block).call( - abi.encodePacked(L1BlockInterop.setL1BlockValuesInterop.selector, setValuesEcotoneCalldata) + abi.encodePacked(IL1BlockInterop.setL1BlockValuesInterop.selector, setValuesEcotoneCalldata) ); assertTrue(success, "function call failed"); @@ -284,7 +284,7 @@ contract L1BlockInteropSetL1BlockValuesInterop_Test is L1BlockInteropTest { contract L1BlockDepositsComplete_Test is L1BlockInteropTest { // @dev Tests that `depositsComplete` reverts if the caller is not the depositor. - function test_deposits_is_depositor_reverts(address _caller) external { + function test_depositsComplete_notDepositor_reverts(address _caller) external { vm.assume(_caller != _l1BlockInterop().DEPOSITOR_ACCOUNT()); vm.expectRevert(NotDepositor.selector); _l1BlockInterop().depositsComplete(); diff --git a/packages/contracts-bedrock/test/L2/L1FeeVault.t.sol b/packages/contracts-bedrock/test/L2/L1FeeVault.t.sol index 03a3e7e5ad9be..5284054056d1b 100644 --- a/packages/contracts-bedrock/test/L2/L1FeeVault.t.sol +++ b/packages/contracts-bedrock/test/L2/L1FeeVault.t.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.15; // Testing utilities -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Types } from "src/libraries/Types.sol"; // Test the implementations of the FeeVault -contract FeeVault_Test is Bridge_Initializer { +contract FeeVault_Test is CommonTest { /// @dev Tests that the constructor sets the correct values. function test_constructor_l1FeeVault_succeeds() external view { assertEq(l1FeeVault.RECIPIENT(), deploy.cfg().l1FeeVaultRecipient()); diff --git a/packages/contracts-bedrock/test/L2/L2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L2/L2CrossDomainMessenger.t.sol index 26fc9f2d039d5..33b8e0bfb8813 100644 --- a/packages/contracts-bedrock/test/L2/L2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L2/L2CrossDomainMessenger.t.sol @@ -2,9 +2,10 @@ pragma solidity 0.8.15; // Testing -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; import { Reverter } from "test/mocks/Callers.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +import { stdError } from "forge-std/StdError.sol"; // Libraries import { Hashing } from "src/libraries/Hashing.sol"; @@ -13,10 +14,10 @@ import { Types } from "src/libraries/Types.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; // Interfaces -import { IL2CrossDomainMessenger } from "src/L2/interfaces/IL2CrossDomainMessenger.sol"; -import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol"; +import { IL2CrossDomainMessenger } from "interfaces/L2/IL2CrossDomainMessenger.sol"; +import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; -contract L2CrossDomainMessenger_Test is Bridge_Initializer { +contract L2CrossDomainMessenger_Test is CommonTest { /// @dev Receiver address for testing address recipient = address(0xabbaacdc); @@ -48,11 +49,9 @@ contract L2CrossDomainMessenger_Test is Bridge_Initializer { Encoding.encodeCrossDomainMessage(l2CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff"); vm.expectCall( address(l2ToL1MessagePasser), - abi.encodeWithSelector( - IL2ToL1MessagePasser.initiateWithdrawal.selector, - address(l1CrossDomainMessenger), - l2CrossDomainMessenger.baseGas(hex"ff", 100), - xDomainCallData + abi.encodeCall( + IL2ToL1MessagePasser.initiateWithdrawal, + (address(l1CrossDomainMessenger), l2CrossDomainMessenger.baseGas(hex"ff", 100), xDomainCallData) ) ); @@ -150,17 +149,103 @@ contract L2CrossDomainMessenger_Test is Bridge_Initializer { assertEq(l2CrossDomainMessenger.failedMessages(hash), false); } - /// @dev Tests that `relayMessage` reverts if attempting to relay - /// a message sent to an L1 system contract. - function test_relayMessage_toSystemContract_reverts() external { - address target = address(l2ToL1MessagePasser); + /// @dev Tests that relayMessage reverts if the value sent does not match the amount + function test_relayMessage_fromOtherMessengerValueMismatch_reverts() external { + // set the target to be alice + address target = alice; address sender = address(l1CrossDomainMessenger); address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); bytes memory message = hex"1111"; + // cannot send a message where the amount inputted does not match the msg.value + vm.deal(caller, 10 ether); vm.prank(caller); + vm.expectRevert(stdError.assertionError); + l2CrossDomainMessenger.relayMessage{ value: 10 ether }( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 9 ether, 0, message + ); + } + + /// @dev Tests that relayMessage reverts if a failed message is attempted to be replayed and the caller is the other + /// messenger + function test_relayMessage_fromOtherMessengerFailedMessageReplay_reverts() external { + // set the target to be alice + address target = alice; + address sender = address(l1CrossDomainMessenger); + address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + bytes memory message = hex"1111"; + + // make a failed message + vm.etch(target, hex"fe"); + vm.prank(caller); + l2CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message + ); + + // cannot replay messages when the caller is the other messenger + vm.prank(caller); + vm.expectRevert(stdError.assertionError); + l2CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message + ); + } + + /// @dev Tests that relayMessage reverts if attempting to relay a message + /// sent to self + function test_relayMessage_toSelf_reverts() external { + address sender = address(l1CrossDomainMessenger); + address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + bytes memory message = hex"1111"; + + vm.store(address(optimismPortal), bytes32(0), bytes32(abi.encode(sender))); + + vm.prank(caller); + vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); + l2CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), + sender, + address(l2CrossDomainMessenger), + 0, + 0, + message + ); + } + + /// @dev Tests that relayMessage reverts if attempting to relay a message + /// sent to the l2ToL1MessagePasser address + function test_relayMessage_toL2ToL1MessagePasser_reverts() external { + address sender = address(l1CrossDomainMessenger); + address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + bytes memory message = hex"1111"; + + vm.store(address(optimismPortal), bytes32(0), bytes32(abi.encode(sender))); + + vm.prank(caller); + vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); + l2CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), + sender, + address(l2ToL1MessagePasser), + 0, + 0, + message + ); + } + + /// @dev Tests that the relayMessage function reverts if the message called by non-optimismPortal but not a failed + /// message + function test_relayMessage_relayingNewMessageByExternalUser_reverts() external { + address target = address(alice); + address sender = address(l1CrossDomainMessenger); + bytes memory message = hex"1111"; + + vm.store(address(optimismPortal), bytes32(0), bytes32(abi.encode(sender))); + + vm.prank(bob); vm.expectRevert("CrossDomainMessenger: message cannot be replayed"); - l1CrossDomainMessenger.relayMessage(Encoding.encodeVersionedNonce(0, 1), sender, target, 0, 0, message); + l2CrossDomainMessenger.relayMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message + ); } /// @dev Tests that `relayMessage` correctly resets the `xDomainMessageSender` @@ -228,19 +313,17 @@ contract L2CrossDomainMessenger_Test is Bridge_Initializer { } /// @dev Tests that sendMessage succeeds with a custom gas token when the call value is zero. - function test_sendMessage_customGasToken_noValue_succeeds() external { + function test_sendMessage_customGasTokenButNoValue_succeeds() external { // Mock the gasPayingToken function to return a custom gas token - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); bytes memory xDomainCallData = Encoding.encodeCrossDomainMessage(l2CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff"); vm.expectCall( address(l2ToL1MessagePasser), - abi.encodeWithSelector( - IL2ToL1MessagePasser.initiateWithdrawal.selector, - address(l1CrossDomainMessenger), - l2CrossDomainMessenger.baseGas(hex"ff", 100), - xDomainCallData + abi.encodeCall( + IL2ToL1MessagePasser.initiateWithdrawal, + (address(l1CrossDomainMessenger), l2CrossDomainMessenger.baseGas(hex"ff", 100), xDomainCallData) ) ); @@ -270,18 +353,18 @@ contract L2CrossDomainMessenger_Test is Bridge_Initializer { } /// @dev Tests that the sendMessage reverts when call value is non-zero with custom gas token. - function test_sendMessage_customGasToken_withValue_reverts() external { + function test_sendMessage_customGasTokenWithValue_reverts() external { // Mock the gasPayingToken function to return a custom gas token - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); vm.expectRevert("CrossDomainMessenger: cannot send value with custom gas token"); l2CrossDomainMessenger.sendMessage{ value: 1 }(recipient, hex"ff", uint32(100)); } /// @dev Tests that the relayMessage succeeds with a custom gas token when the call value is zero. - function test_relayMessage_customGasToken_noValue_succeeds() external { + function test_relayMessage_customGasTokenAndNoValue_succeeds() external { // Mock the gasPayingToken function to return a custom gas token - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); address target = address(0xabcd); address sender = address(l1CrossDomainMessenger); @@ -315,9 +398,9 @@ contract L2CrossDomainMessenger_Test is Bridge_Initializer { /// @dev Tests that the relayMessage reverts when call value is non-zero with custom gas token. /// The L1CrossDomainMessenger `sendMessage` function cannot send value with a custom gas token. - function test_relayMessage_customGasToken_withValue_reverts() external virtual { + function test_relayMessage_customGasTokenWithValue_reverts() external virtual { // Mock the gasPayingToken function to return a custom gas token - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); vm.expectRevert("CrossDomainMessenger: value must be zero unless message is from a system address"); l2CrossDomainMessenger.relayMessage{ value: 1 }( diff --git a/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol b/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol index 6ee1312d94d77..1fbb2cae0b88a 100644 --- a/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol @@ -2,15 +2,15 @@ pragma solidity 0.8.15; // Testing -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Contracts import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { OptimismMintableERC721 } from "src/universal/OptimismMintableERC721.sol"; // Interfaces -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL2ERC721Bridge } from "interfaces/L2/IL2ERC721Bridge.sol"; contract TestERC721 is ERC721 { constructor() ERC721("Test", "TST") { } @@ -33,7 +33,7 @@ contract TestMintableERC721 is OptimismMintableERC721 { } } -contract L2ERC721Bridge_Test is Bridge_Initializer { +contract L2ERC721Bridge_Test is CommonTest { TestMintableERC721 internal localToken; TestERC721 internal remoteToken; uint256 internal constant tokenId = 1; @@ -227,6 +227,14 @@ contract L2ERC721Bridge_Test is Bridge_Initializer { assertEq(localToken.ownerOf(tokenId), alice); } + /// @dev Tests that `bridgeERC721To` reverts if the to address is the zero address. + function test_bridgeERC721To_toZeroAddress_reverts() external { + // Bridge the token. + vm.prank(bob); + vm.expectRevert("ERC721Bridge: nft recipient cannot be address(0)"); + l2ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), address(0), tokenId, 1234, hex"5678"); + } + /// @dev Tests that `finalizeBridgeERC721` correctly finalizes a bridged token. function test_finalizeBridgeERC721_succeeds() external { // Bridge the token. @@ -240,7 +248,7 @@ contract L2ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(l1ERC721Bridge) ); vm.prank(address(l2CrossDomainMessenger)); @@ -264,7 +272,7 @@ contract L2ERC721Bridge_Test is Bridge_Initializer { // to be compliant with the `IOptimismMintableERC721` interface. vm.mockCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(l1ERC721Bridge) ); vm.prank(address(l2CrossDomainMessenger)); @@ -287,7 +295,7 @@ contract L2ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(alice) ); vm.prank(address(l2CrossDomainMessenger)); @@ -301,7 +309,7 @@ contract L2ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1ERC721Bridge)) ); vm.prank(address(l2CrossDomainMessenger)); @@ -316,7 +324,7 @@ contract L2ERC721Bridge_Test is Bridge_Initializer { // Finalize a withdrawal. vm.mockCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1ERC721Bridge)) ); vm.prank(address(l2CrossDomainMessenger)); diff --git a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol index ee993fe1110ce..aa5813532d2b7 100644 --- a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol @@ -22,32 +22,21 @@ contract L2GenesisTest is Test { /// @notice Creates a temp file and returns the path to it. function tmpfile() internal returns (string memory) { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = "mktemp"; - bytes memory result = Process.run(commands); - return string(result); + return Process.bash("mktemp"); } /// @notice Deletes a file at a given filesystem path. Does not force delete /// and does not recursively delete. function deleteFile(string memory path) internal { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat("rm ", path); - Process.run({ _command: commands, _allowEmpty: true }); + Process.bash(string.concat("rm ", path), true); } /// @notice Returns the number of top level keys in a JSON object at a given /// file path. function getJSONKeyCount(string memory path) internal returns (uint256) { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat("jq 'keys | length' < ", path, " | xargs cast abi-encode 'f(uint256)'"); - return abi.decode(Process.run(commands), (uint256)); + bytes memory result = + bytes(Process.bash(string.concat("jq 'keys | length' < ", path, " | xargs cast abi-encode 'f(uint256)'"))); + return abi.decode(result, (uint256)); } /// @notice Helper function to run a function with a temporary dump file. @@ -59,43 +48,44 @@ contract L2GenesisTest is Test { /// @notice Helper function for reading the number of storage keys for a given account. function getStorageKeysCount(string memory _path, address _addr) internal returns (uint256) { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = - string.concat("jq -r '.[\"", vm.toLowercase(vm.toString(_addr)), "\"].storage | length' < ", _path); - return vm.parseUint(string(Process.run(commands))); + return vm.parseUint( + Process.bash( + string.concat("jq -r '.[\"", vm.toLowercase(vm.toString(_addr)), "\"].storage | length' < ", _path) + ) + ); } /// @notice Returns the number of accounts that contain particular code at a given path to a genesis file. function getCodeCount(string memory path, string memory name) internal returns (uint256) { bytes memory code = vm.getDeployedCode(name); - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat( - "jq -r 'map_values(select(.code == \"", - vm.toString(code), - "\")) | length' < ", - path, - " | xargs cast abi-encode 'f(uint256)'" + bytes memory result = bytes( + Process.bash( + string.concat( + "jq -r 'map_values(select(.code == \"", + vm.toString(code), + "\")) | length' < ", + path, + " | xargs cast abi-encode 'f(uint256)'" + ) + ) ); - return abi.decode(Process.run(commands), (uint256)); + return abi.decode(result, (uint256)); } /// @notice Returns the number of accounts that have a particular slot set. function getPredeployCountWithSlotSet(string memory path, bytes32 slot) internal returns (uint256) { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat( - "jq 'map_values(.storage | select(has(\"", - vm.toString(slot), - "\"))) | keys | length' < ", - path, - " | xargs cast abi-encode 'f(uint256)'" + bytes memory result = bytes( + Process.bash( + string.concat( + "jq 'map_values(.storage | select(has(\"", + vm.toString(slot), + "\"))) | keys | length' < ", + path, + " | xargs cast abi-encode 'f(uint256)'" + ) + ) ); - return abi.decode(Process.run(commands), (uint256)); + return abi.decode(result, (uint256)); } /// @notice Returns the number of accounts that have a particular slot set to a particular value. @@ -107,30 +97,31 @@ contract L2GenesisTest is Test { internal returns (uint256) { - string[] memory commands = new string[](3); - commands[0] = "bash"; - commands[1] = "-c"; - commands[2] = string.concat( - "jq 'map_values(.storage | select(.\"", - vm.toString(slot), - "\" == \"", - vm.toString(value), - "\")) | length' < ", - path, - " | xargs cast abi-encode 'f(uint256)'" + bytes memory result = bytes( + Process.bash( + string.concat( + "jq 'map_values(.storage | select(.\"", + vm.toString(slot), + "\" == \"", + vm.toString(value), + "\")) | length' < ", + path, + " | xargs cast abi-encode 'f(uint256)'" + ) + ) ); - return abi.decode(Process.run(commands), (uint256)); + return abi.decode(result, (uint256)); } /// @notice Tests the genesis predeploys setup using a temp file for the case where useInterop is false. - function test_genesis_predeploys_notUsingInterop() external { + function test_genesisPredeploys_notUsingInterop_works() external { string memory path = tmpfile(); _test_genesis_predeploys(path, false); deleteFile(path); } /// @notice Tests the genesis predeploys setup using a temp file for the case where useInterop is true. - function test_genesis_predeploys_usingInterop() external { + function test_genesisPredeploys_usingInterop_works() external { string memory path = tmpfile(); _test_genesis_predeploys(path, true); deleteFile(path); @@ -139,9 +130,7 @@ contract L2GenesisTest is Test { /// @notice Tests the genesis predeploys setup. function _test_genesis_predeploys(string memory _path, bool _useInterop) internal { // Set the useInterop value - vm.mockCall( - address(genesis.cfg()), abi.encodeWithSelector(genesis.cfg().useInterop.selector), abi.encode(_useInterop) - ); + vm.mockCall(address(genesis.cfg()), abi.encodeCall(genesis.cfg().useInterop, ()), abi.encode(_useInterop)); // Set the predeploy proxies into state genesis.setPredeployProxies(); @@ -150,8 +139,8 @@ contract L2GenesisTest is Test { // 2 predeploys do not have proxies assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2); - // 23 proxies have the implementation set if useInterop is true and 17 if useInterop is false - assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 23 : 17); + // 25 proxies have the implementation set if useInterop is true and 18 if useInterop is false + assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 25 : 18); // All proxies except 2 have the proxy 1967 admin slot set to the proxy admin assertEq( @@ -165,12 +154,12 @@ contract L2GenesisTest is Test { } /// @notice Tests the number of accounts in the genesis setup - function test_allocs_size() external { + function test_allocs_size_works() external { withTempDump(_test_allocs_size); } /// @notice Creates mock L1Dependencies for testing purposes. - function _dummyL1Deps() internal pure returns (L1Dependencies memory _deps) { + function _dummyL1Deps() internal pure returns (L1Dependencies memory deps_) { return L1Dependencies({ l1CrossDomainMessengerProxy: payable(address(0x100000)), l1StandardBridgeProxy: payable(address(0x100001)), @@ -180,16 +169,25 @@ contract L2GenesisTest is Test { /// @notice Tests the number of accounts in the genesis setup function _test_allocs_size(string memory _path) internal { + vm.mockCall(address(genesis.cfg()), abi.encodeCall(genesis.cfg().useSoulGasToken, ()), abi.encode(true)); + vm.mockCall(address(genesis.cfg()), abi.encodeCall(genesis.cfg().isSoulBackedByNative, ()), abi.encode(true)); + genesis.cfg().setFundDevAccounts(false); genesis.runWithLatestLocal(_dummyL1Deps()); genesis.writeGenesisAllocs(_path); uint256 expected = 0; - expected += 2048 - 2; // predeploy proxies - expected += 21; // predeploy implementations (excl. legacy erc20-style eth and legacy message sender) + // predeploy proxies; WETH and GovernanceToken are not behind proxies + expected += Predeploys.PREDEPLOY_COUNT - 2; + // predeploy implementations (excl. legacy erc20-style eth and legacy message sender) + // setPredeployImplementations function in L2Genesis.s.sol sets 20 predeploy implementations (useInterop == + // false) + expected += 20; expected += 256; // precompiles - expected += 13; // preinstalls + // setPreinstalls function in SetPreinstalls.s.sol sets 15 preinstalls + expected += 15; // preinstalls expected += 1; // 4788 deployer account + // 16 prefunded dev accounts are excluded assertEq(expected, getJSONKeyCount(_path), "key count check"); diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol index 3a173e9743b45..f7b61083e249d 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.15; // Testing import { stdStorage, StdStorage } from "forge-std/Test.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -16,12 +16,12 @@ import { Hashing } from "src/libraries/Hashing.sol"; import { Types } from "src/libraries/Types.sol"; // Interfaces -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; -import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol"; -import { IL2StandardBridge } from "src/L2/interfaces/IL2StandardBridge.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; +import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; +import { IL2StandardBridge } from "interfaces/L2/IL2StandardBridge.sol"; -contract L2StandardBridge_Test is Bridge_Initializer { +contract L2StandardBridge_Test is CommonTest { using stdStorage for StdStorage; /// @dev Test that the bridge's constructor sets the correct values. @@ -34,6 +34,7 @@ contract L2StandardBridge_Test is Bridge_Initializer { assertEq(address(impl.messenger()), Predeploys.L2_CROSS_DOMAIN_MESSENGER, "constructor zero check messenger"); assertEq(address(impl.OTHER_BRIDGE()), address(0), "constructor zero check OTHER_BRIDGE"); assertEq(address(impl.otherBridge()), address(0), "constructor zero check otherBridge"); + assertEq(address(impl.l1TokenBridge()), address(0), "constructor zero check l1TokenBridge"); } /// @dev Tests that the bridge is initialized correctly. @@ -56,17 +57,11 @@ contract L2StandardBridge_Test is Bridge_Initializer { assertEq(address(l2ToL1MessagePasser).balance, 0); uint256 nonce = l2CrossDomainMessenger.messageNonce(); - bytes memory message = - abi.encodeWithSelector(IStandardBridge.finalizeBridgeETH.selector, alice, alice, 100, hex""); + bytes memory message = abi.encodeCall(IStandardBridge.finalizeBridgeETH, (alice, alice, 100, hex"")); uint64 baseGas = l2CrossDomainMessenger.baseGas(message, 200_000); - bytes memory withdrawalData = abi.encodeWithSelector( - ICrossDomainMessenger.relayMessage.selector, - nonce, - address(l2StandardBridge), - address(l1StandardBridge), - 100, - 200_000, - message + bytes memory withdrawalData = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + (nonce, address(l2StandardBridge), address(l1StandardBridge), 100, 200_000, message) ); bytes32 withdrawalHash = Hashing.hashWithdrawal( Types.WithdrawalTransaction({ @@ -107,21 +102,16 @@ contract L2StandardBridge_Test is Bridge_Initializer { vm.expectCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, - address(l1StandardBridge), - message, - 200_000 // StandardBridge's RECEIVE_DEFAULT_GAS_LIMIT + abi.encodeCall( + ICrossDomainMessenger.sendMessage, + (address(l1StandardBridge), message, 200_000) // StandardBridge's RECEIVE_DEFAULT_GAS_LIMIT ) ); vm.expectCall( Predeploys.L2_TO_L1_MESSAGE_PASSER, - abi.encodeWithSelector( - IL2ToL1MessagePasser.initiateWithdrawal.selector, - address(l1CrossDomainMessenger), - baseGas, - withdrawalData + abi.encodeCall( + IL2ToL1MessagePasser.initiateWithdrawal, (address(l1CrossDomainMessenger), baseGas, withdrawalData) ) ); @@ -134,7 +124,7 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that the receive function reverts with custom gas token. function testFuzz_receive_customGasToken_reverts(uint256 _value) external { vm.prank(alice, alice); - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); vm.deal(alice, _value); (bool success, bytes memory data) = address(l2StandardBridge).call{ value: _value }(hex""); assertFalse(success); @@ -173,7 +163,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdraw` reverts with custom gas token. function test_withdraw_customGasToken_reverts() external { - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdraw(address(Predeploys.LEGACY_ERC20_ETH), 1, 1, hex""); @@ -181,7 +173,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdraw` reverts with custom gas token. function test_withdrawERC20_customGasToken_reverts() external { - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdraw(address(L1Token), 1, 1, hex""); @@ -190,7 +184,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdraw` reverts with custom gas token. function test_withdrawERC20WithValue_customGasToken_reverts() external { vm.deal(alice, 1 ether); - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdraw{ value: 1 ether }(address(L1Token), 1, 1, hex""); @@ -199,7 +195,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdraw` with value reverts with custom gas token. function test_withdraw_customGasTokenWithValue_reverts() external { vm.deal(alice, 1 ether); - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdraw{ value: 1 ether }(address(Predeploys.LEGACY_ERC20_ETH), 1, 1, hex""); @@ -207,7 +205,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdrawTo` reverts with custom gas token. function test_withdrawTo_customGasToken_reverts() external { - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdrawTo(address(Predeploys.LEGACY_ERC20_ETH), bob, 1, 1, hex""); @@ -215,7 +215,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdrawTo` reverts with custom gas token. function test_withdrawToERC20_customGasToken_reverts() external { - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdrawTo(address(L2Token), bob, 1, 1, hex""); @@ -224,7 +226,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdrawTo` reverts with custom gas token. function test_withdrawToERC20WithValue_customGasToken_reverts() external { vm.deal(alice, 1 ether); - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdrawTo{ value: 1 ether }(address(L2Token), bob, 1, 1, hex""); @@ -233,7 +237,9 @@ contract L2StandardBridge_Test is Bridge_Initializer { /// @dev Tests that `withdrawTo` with value reverts with custom gas token. function test_withdrawTo_customGasTokenWithValue_reverts() external { vm.deal(alice, 1 ether); - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))); + vm.mockCall( + address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(18)) + ); vm.expectRevert("L2StandardBridge: not supported with custom gas token"); vm.prank(alice, alice); l2StandardBridge.withdrawTo{ value: 1 ether }(address(Predeploys.LEGACY_ERC20_ETH), bob, 1, 1, hex""); @@ -270,25 +276,19 @@ contract L2StandardBridge_Test is Bridge_Initializer { } } -contract PreBridgeERC20 is Bridge_Initializer { +contract PreBridgeERC20 is CommonTest { /// @dev Sets up expected calls and emits for a successful ERC20 withdrawal. function _preBridgeERC20(bool _isLegacy, address _l2Token) internal { // Alice has 100 L2Token deal(_l2Token, alice, 100, true); assertEq(ERC20(_l2Token).balanceOf(alice), 100); uint256 nonce = l2CrossDomainMessenger.messageNonce(); - bytes memory message = abi.encodeWithSelector( - IStandardBridge.finalizeBridgeERC20.selector, address(L1Token), _l2Token, alice, alice, 100, hex"" - ); + bytes memory message = + abi.encodeCall(IStandardBridge.finalizeBridgeERC20, (address(L1Token), _l2Token, alice, alice, 100, hex"")); uint64 baseGas = l2CrossDomainMessenger.baseGas(message, 1000); - bytes memory withdrawalData = abi.encodeWithSelector( - ICrossDomainMessenger.relayMessage.selector, - nonce, - address(l2StandardBridge), - address(l1StandardBridge), - 0, - 1000, - message + bytes memory withdrawalData = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + (nonce, address(l2StandardBridge), address(l1StandardBridge), 0, 1000, message) ); bytes32 withdrawalHash = Hashing.hashWithdrawal( Types.WithdrawalTransaction({ @@ -303,35 +303,29 @@ contract PreBridgeERC20 is Bridge_Initializer { if (_isLegacy) { vm.expectCall( - address(l2StandardBridge), - abi.encodeWithSelector(l2StandardBridge.withdraw.selector, _l2Token, 100, 1000, hex"") + address(l2StandardBridge), abi.encodeCall(l2StandardBridge.withdraw, (_l2Token, 100, 1000, hex"")) ); } else { vm.expectCall( address(l2StandardBridge), - abi.encodeWithSelector( - l2StandardBridge.bridgeERC20.selector, _l2Token, address(L1Token), 100, 1000, hex"" - ) + abi.encodeCall(l2StandardBridge.bridgeERC20, (_l2Token, address(L1Token), 100, 1000, hex"")) ); } vm.expectCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector(ICrossDomainMessenger.sendMessage.selector, address(l1StandardBridge), message, 1000) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1StandardBridge), message, 1000)) ); vm.expectCall( Predeploys.L2_TO_L1_MESSAGE_PASSER, - abi.encodeWithSelector( - IL2ToL1MessagePasser.initiateWithdrawal.selector, - address(l1CrossDomainMessenger), - baseGas, - withdrawalData + abi.encodeCall( + IL2ToL1MessagePasser.initiateWithdrawal, (address(l1CrossDomainMessenger), baseGas, withdrawalData) ) ); // The l2StandardBridge should burn the tokens - vm.expectCall(_l2Token, abi.encodeWithSelector(OptimismMintableERC20.burn.selector, alice, 100)); + vm.expectCall(_l2Token, abi.encodeCall(OptimismMintableERC20.burn, (alice, 100))); vm.expectEmit(true, true, true, true); emit WithdrawalInitiated(address(L1Token), _l2Token, alice, alice, 100, hex""); @@ -385,6 +379,12 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 { assertEq(L2Token.balanceOf(alice), 0); } + function test_bridgeERC20_isNotCorrectTokenPair_reverts() external { + vm.expectRevert("StandardBridge: wrong remote token for Optimism Mintable ERC20 local token"); + vm.prank(alice, alice); + l2StandardBridge.bridgeERC20(address(L2Token), address(BadL1Token), 100, 1000, hex""); + } + function test_withdrawLegacyERC20_succeeds() external { _preBridgeERC20({ _isLegacy: true, _l2Token: address(LegacyL2Token) }); l2StandardBridge.withdraw(address(LegacyL2Token), 100, 1000, hex""); @@ -408,25 +408,19 @@ contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 { } } -contract PreBridgeERC20To is Bridge_Initializer { +contract PreBridgeERC20To is CommonTest { // withdrawTo and BridgeERC20To should behave the same when transferring ERC20 tokens // so they should share the same setup and expectEmit calls function _preBridgeERC20To(bool _isLegacy, address _l2Token) internal { deal(_l2Token, alice, 100, true); assertEq(ERC20(L2Token).balanceOf(alice), 100); uint256 nonce = l2CrossDomainMessenger.messageNonce(); - bytes memory message = abi.encodeWithSelector( - IStandardBridge.finalizeBridgeERC20.selector, address(L1Token), _l2Token, alice, bob, 100, hex"" - ); + bytes memory message = + abi.encodeCall(IStandardBridge.finalizeBridgeERC20, (address(L1Token), _l2Token, alice, bob, 100, hex"")); uint64 baseGas = l2CrossDomainMessenger.baseGas(message, 1000); - bytes memory withdrawalData = abi.encodeWithSelector( - ICrossDomainMessenger.relayMessage.selector, - nonce, - address(l2StandardBridge), - address(l1StandardBridge), - 0, - 1000, - message + bytes memory withdrawalData = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + (nonce, address(l2StandardBridge), address(l1StandardBridge), 0, 1000, message) ); bytes32 withdrawalHash = Hashing.hashWithdrawal( Types.WithdrawalTransaction({ @@ -467,34 +461,29 @@ contract PreBridgeERC20To is Bridge_Initializer { if (_isLegacy) { vm.expectCall( address(l2StandardBridge), - abi.encodeWithSelector(l2StandardBridge.withdrawTo.selector, _l2Token, bob, 100, 1000, hex"") + abi.encodeCall(l2StandardBridge.withdrawTo, (_l2Token, bob, 100, 1000, hex"")) ); } else { vm.expectCall( address(l2StandardBridge), - abi.encodeWithSelector( - l2StandardBridge.bridgeERC20To.selector, _l2Token, address(L1Token), bob, 100, 1000, hex"" - ) + abi.encodeCall(l2StandardBridge.bridgeERC20To, (_l2Token, address(L1Token), bob, 100, 1000, hex"")) ); } vm.expectCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector(ICrossDomainMessenger.sendMessage.selector, address(l1StandardBridge), message, 1000) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1StandardBridge), message, 1000)) ); vm.expectCall( Predeploys.L2_TO_L1_MESSAGE_PASSER, - abi.encodeWithSelector( - IL2ToL1MessagePasser.initiateWithdrawal.selector, - address(l1CrossDomainMessenger), - baseGas, - withdrawalData + abi.encodeCall( + IL2ToL1MessagePasser.initiateWithdrawal, (address(l1CrossDomainMessenger), baseGas, withdrawalData) ) ); // The l2StandardBridge should burn the tokens - vm.expectCall(address(L2Token), abi.encodeWithSelector(OptimismMintableERC20.burn.selector, alice, 100)); + vm.expectCall(address(L2Token), abi.encodeCall(OptimismMintableERC20.burn, (alice, 100))); vm.prank(alice, alice); } @@ -519,12 +508,12 @@ contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To { } } -contract L2StandardBridge_Bridge_Test is Bridge_Initializer { +contract L2StandardBridge_Bridge_Test is CommonTest { /// @dev Tests that `finalizeBridgeETH` reverts if the recipient is the other bridge. function test_finalizeBridgeETH_sendToSelf_reverts() external { vm.mockCall( address(l2StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) ); vm.deal(address(l2CrossDomainMessenger), 100); @@ -537,7 +526,7 @@ contract L2StandardBridge_Bridge_Test is Bridge_Initializer { function test_finalizeBridgeETH_sendToMessenger_reverts() external { vm.mockCall( address(l2StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) ); vm.deal(address(l2CrossDomainMessenger), 100); @@ -550,21 +539,16 @@ contract L2StandardBridge_Bridge_Test is Bridge_Initializer { function testFuzz_bridgeETH_succeeds(uint256 _value, uint32 _minGasLimit, bytes calldata _extraData) external { uint256 nonce = l2CrossDomainMessenger.messageNonce(); - bytes memory message = - abi.encodeWithSelector(IStandardBridge.finalizeBridgeETH.selector, alice, alice, _value, _extraData); + bytes memory message = abi.encodeCall(IStandardBridge.finalizeBridgeETH, (alice, alice, _value, _extraData)); vm.expectCall( - address(l2StandardBridge), - _value, - abi.encodeWithSelector(l2StandardBridge.bridgeETH.selector, _minGasLimit, _extraData) + address(l2StandardBridge), _value, abi.encodeCall(l2StandardBridge.bridgeETH, (_minGasLimit, _extraData)) ); vm.expectCall( address(l2CrossDomainMessenger), _value, - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, address(l1StandardBridge), message, _minGasLimit - ) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1StandardBridge), message, _minGasLimit)) ); vm.expectEmit(address(l2StandardBridge)); @@ -587,7 +571,7 @@ contract L2StandardBridge_Bridge_Test is Bridge_Initializer { /// @dev Tests that bridging reverts with custom gas token. function test_bridgeETH_customGasToken_reverts() external { vm.prank(alice, alice); - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); l2StandardBridge.bridgeETH(50000, hex"dead"); @@ -600,19 +584,16 @@ contract L2StandardBridge_Bridge_Test is Bridge_Initializer { vm.expectCall( address(l2StandardBridge), _value, - abi.encodeWithSelector(l1StandardBridge.bridgeETHTo.selector, bob, _minGasLimit, _extraData) + abi.encodeCall(l1StandardBridge.bridgeETHTo, (bob, _minGasLimit, _extraData)) ); - bytes memory message = - abi.encodeWithSelector(IStandardBridge.finalizeBridgeETH.selector, alice, bob, _value, _extraData); + bytes memory message = abi.encodeCall(IStandardBridge.finalizeBridgeETH, (alice, bob, _value, _extraData)); // the L2 bridge should call // L2CrossDomainMessenger.sendMessage vm.expectCall( address(l2CrossDomainMessenger), - abi.encodeWithSelector( - ICrossDomainMessenger.sendMessage.selector, address(l1StandardBridge), message, _minGasLimit - ) + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1StandardBridge), message, _minGasLimit)) ); vm.expectEmit(address(l2StandardBridge)); @@ -641,20 +622,20 @@ contract L2StandardBridge_Bridge_Test is Bridge_Initializer { ) external { - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); vm.deal(address(this), _value); l2StandardBridge.bridgeETHTo{ value: _value }(bob, _minGasLimit, _extraData); } } -contract L2StandardBridge_FinalizeBridgeETH_Test is Bridge_Initializer { +contract L2StandardBridge_FinalizeBridgeETH_Test is CommonTest { /// @dev Tests that `finalizeBridgeETH` succeeds. function test_finalizeBridgeETH_succeeds() external { address messenger = address(l2StandardBridge.messenger()); vm.mockCall( messenger, - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) ); vm.deal(messenger, 100); @@ -674,14 +655,54 @@ contract L2StandardBridge_FinalizeBridgeETH_Test is Bridge_Initializer { address messenger = address(l2StandardBridge.messenger()); vm.mockCall( messenger, - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) ); vm.deal(address(l2CrossDomainMessenger), 1); vm.prank(address(l2CrossDomainMessenger)); - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))); + vm.mockCall(address(l1Block), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(1), uint8(2))); vm.expectRevert("StandardBridge: cannot bridge ETH with custom gas token"); l2StandardBridge.finalizeBridgeETH(alice, alice, 1, hex""); } } + +contract L2StandardBridge_FinalizeBridgeERC20_Test is CommonTest { + /// @dev Tests that `finalizeBridgeERC20` succeeds. + function test_finalizeBridgeERC20_succeeds() external { + address messenger = address(l2StandardBridge.messenger()); + address localToken = address(L2Token); + address remoteToken = address(L1Token); + vm.mockCall( + messenger, + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), + abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) + ); + deal(localToken, messenger, 100, true); + vm.prank(messenger); + + vm.expectEmit(true, true, true, true); + emit DepositFinalized(remoteToken, localToken, alice, alice, 100, hex""); + + vm.expectEmit(true, true, true, true); + emit ERC20BridgeFinalized(localToken, remoteToken, alice, alice, 100, hex""); + + l2StandardBridge.finalizeBridgeERC20(localToken, remoteToken, alice, alice, 100, hex""); + } + + function test_finalizeBridgeERC20_isNotCorrectTokenPair_reverts() external { + address messenger = address(l2StandardBridge.messenger()); + address localToken = address(L2Token); + address remoteToken = address(BadL1Token); + vm.mockCall( + messenger, + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), + abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) + ); + deal(localToken, messenger, 100, true); + vm.prank(messenger); + + vm.expectRevert("StandardBridge: wrong remote token for Optimism Mintable ERC20 local token"); + l2StandardBridge.finalizeBridgeERC20(localToken, remoteToken, alice, alice, 100, hex""); + } +} diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index 819b3562c98ef..a57c38a644d79 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -2,17 +2,18 @@ pragma solidity 0.8.15; // Testing -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Interfaces -import { IL2StandardBridgeInterop, IMintableAndBurnable } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; +import { IMintableAndBurnableERC20 } from "interfaces/L2/IMintableAndBurnableERC20.sol"; +import { IL2StandardBridgeInterop } from "interfaces/L2/IL2StandardBridgeInterop.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; +import { IOptimismMintableERC20 } from "interfaces/universal/IOptimismMintableERC20.sol"; import { ILegacyMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IOptimismERC20Factory } from "interfaces/L2/IOptimismERC20Factory.sol"; -contract L2StandardBridgeInterop_Test is Bridge_Initializer { +contract L2StandardBridgeInterop_Test is CommonTest { /// @notice Emitted when a conversion is made. event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); @@ -30,21 +31,17 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer { /// @notice Mock ERC20 decimals function _mockDecimals(address _token, uint8 _decimals) internal { - _mockAndExpect(_token, abi.encodeWithSelector(IERC20Metadata.decimals.selector), abi.encode(_decimals)); + _mockAndExpect(_token, abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(_decimals)); } /// @notice Mock ERC165 interface function _mockInterface(address _token, bytes4 _interfaceId, bool _supported) internal { - _mockAndExpect( - _token, abi.encodeWithSelector(IERC165.supportsInterface.selector, _interfaceId), abi.encode(_supported) - ); + _mockAndExpect(_token, abi.encodeCall(IERC165.supportsInterface, (_interfaceId)), abi.encode(_supported)); } /// @notice Mock factory deployment function _mockDeployments(address _factory, address _token, address _deployed) internal { - _mockAndExpect( - _factory, abi.encodeWithSelector(IOptimismERC20Factory.deployments.selector, _token), abi.encode(_deployed) - ); + _mockAndExpect(_factory, abi.encodeCall(IOptimismERC20Factory.deployments, (_token)), abi.encode(_deployed)); } /// @notice Assume a valid address for fuzzing @@ -53,9 +50,9 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer { } } -/// @notice Test suite when converting from a legacy token to a SuperchainERC20 token +/// @notice Test suite when converting from a legacy token to a OptimismSuperchainERC20 token contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a legacy token to a SuperchainERC20 token + /// @notice Set up the test for converting from a legacy token to a OptimismSuperchainERC20 token function _setUpLegacyToSuper(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -197,10 +194,8 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T emit Converted(_from, _to, _caller, _amount); // Mock and expect the `burn` and `mint` functions - _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() - ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); + _mockAndExpect(_from, abi.encodeCall(IMintableAndBurnableERC20.burn, (_caller, _amount)), abi.encode()); + _mockAndExpect(_to, abi.encodeCall(IMintableAndBurnableERC20.mint, (_caller, _amount)), abi.encode()); // Act vm.prank(_caller); @@ -208,9 +203,9 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T } } -/// @notice Test suite when converting from a SuperchainERC20 token to a legacy token +/// @notice Test suite when converting from a OptimismSuperchainERC20 token to a legacy token contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a SuperchainERC20 token to a legacy token + /// @notice Set up the test for converting from a OptimismSuperchainERC20 token to a legacy token function _setUpSuperToLegacy(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -353,10 +348,8 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T emit Converted(_from, _to, _caller, _amount); // Mock and expect the `burn` and `mint` functions - _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() - ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); + _mockAndExpect(_from, abi.encodeCall(IMintableAndBurnableERC20.burn, (_caller, _amount)), abi.encode()); + _mockAndExpect(_to, abi.encodeCall(IMintableAndBurnableERC20.mint, (_caller, _amount)), abi.encode()); // Act vm.prank(_caller); diff --git a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol index f5ff43c832ca2..3b43148536900 100644 --- a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol @@ -10,8 +10,6 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { Hashing } from "src/libraries/Hashing.sol"; // Target contract -import { CrossL2Inbox } from "src/L2/CrossL2Inbox.sol"; -import { ICrossL2Inbox } from "src/L2/interfaces/ICrossL2Inbox.sol"; import { L2ToL2CrossDomainMessenger, NotEntered, @@ -22,9 +20,15 @@ import { MessageTargetCrossL2Inbox, MessageTargetL2ToL2CrossDomainMessenger, MessageAlreadyRelayed, - ReentrantCall + ReentrantCall, + TargetCallFailed, + IDependencySet, + InvalidChainId } from "src/L2/L2ToL2CrossDomainMessenger.sol"; +// Interfaces +import { ICrossL2Inbox, Identifier } from "interfaces/L2/ICrossL2Inbox.sol"; + /// @title L2ToL2CrossDomainMessengerWithModifiableTransientStorage /// @dev L2ToL2CrossDomainMessenger contract with methods to modify the transient storage. /// This is used to test the transient storage of L2ToL2CrossDomainMessenger. @@ -85,6 +89,13 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure that the target contract is not CrossL2Inbox or L2ToL2CrossDomainMessenger vm.assume(_target != Predeploys.CROSS_L2_INBOX && _target != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + // Mock the call over the `isInDependencySet` function to return true + vm.mockCall( + Predeploys.L1_BLOCK_ATTRIBUTES, + abi.encodeCall(IDependencySet.isInDependencySet, (_destination)), + abi.encode(true) + ); + // Get the current message nonce uint256 messageNonce = l2ToL2CrossDomainMessenger.messageNonce(); @@ -193,6 +204,34 @@ contract L2ToL2CrossDomainMessengerTest is Test { }); } + /// @notice Tests the `sendMessage` function reverts when the `destination` is not in the dependency set. + function testFuzz_sendMessage_notInDependencySet_reverts( + uint256 _destination, + address _target, + bytes calldata _message + ) + external + { + // Ensure the destination is not the same as the source, otherwise the function will revert + vm.assume(_destination != block.chainid); + + // Ensure that the target contract is not CrossL2Inbox or L2ToL2CrossDomainMessenger + vm.assume(_target != Predeploys.CROSS_L2_INBOX && _target != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + // Mock the call over the `isInDependencySet` function to return false + vm.mockCall( + Predeploys.L1_BLOCK_ATTRIBUTES, + abi.encodeCall(IDependencySet.isInDependencySet, (_destination)), + abi.encode(false) + ); + + // Expect a revert with the InvalidChainId selector + vm.expectRevert(InvalidChainId.selector); + + // Call `sendMessage` with a destination that is not in the dependency set to provoke revert + l2ToL2CrossDomainMessenger.sendMessage(_destination, _target, _message); + } + /// @dev Tests that the `relayMessage` function succeeds and emits the correct RelayedMessage event. function testFuzz_relayMessage_succeeds( uint256 _source, @@ -217,8 +256,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { vm.mockCall({ callee: _target, msgValue: _value, data: _message, returnData: abi.encode(true) }); // Construct the SentMessage payload & identifier - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics abi.encode(_sender, _message) // data @@ -227,7 +266,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); @@ -263,15 +302,15 @@ contract L2ToL2CrossDomainMessengerTest is Test { vm.expectRevert(EventPayloadNotSentMessage.selector); // Point to a different remote log that the inbox validates - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encode(L2ToL2CrossDomainMessenger.RelayedMessage.selector, _source, _nonce, _msgHash); // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); @@ -312,7 +351,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Set the target and message for the reentrant call address target = address(this); - bytes memory message = abi.encodeWithSelector(this.mockTarget.selector, _source, _sender); + bytes memory message = abi.encodeCall(this.mockTarget, (_source, _sender)); bytes32 msgHash = keccak256(abi.encode(block.chainid, _source, _nonce, _sender, target, message)); @@ -324,8 +363,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { vm.expectCall({ callee: target, msgValue: _value, data: message }); // Construct and relay the message - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, target, _nonce), // topics abi.encode(_sender, message) // data @@ -334,7 +373,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); @@ -367,8 +406,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { vm.expectRevert(ReentrantCall.selector); - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, 1, 1, 1, _source); + Identifier memory id = Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, 1, 1, 1, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, address(0), _nonce), // topics abi.encode(_sender, "") // data @@ -400,20 +438,14 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Set the target and message for the reentrant call address target = address(this); - bytes memory message = abi.encodeWithSelector(this.mockTargetReentrant.selector, _source2, _nonce, _sender2); - - // Look for correct emitted event - vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - emit L2ToL2CrossDomainMessenger.FailedRelayedMessage( - _source1, _nonce, keccak256(abi.encode(block.chainid, _source1, _nonce, _sender1, target, message)) - ); + bytes memory message = abi.encodeCall(this.mockTargetReentrant, (_source2, _nonce, _sender2)); // Ensure the target contract is called with the correct parameters vm.expectCall({ callee: target, msgValue: _value, data: message }); // Construct and relay the message - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source1); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source1); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, target, _nonce), // topics abi.encode(_sender1, message) // data @@ -422,10 +454,12 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); + // Expect a revert with the TargetCallFailed selector + vm.expectRevert(TargetCallFailed.selector); hoax(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _value); l2ToL2CrossDomainMessenger.relayMessage{ value: _value }(id, sentMessage); @@ -460,7 +494,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Expect a revert with the IdOriginNotL2ToL2CrossDomainMessenger vm.expectRevert(IdOriginNotL2ToL2CrossDomainMessenger.selector); - ICrossL2Inbox.Identifier memory id = ICrossL2Inbox.Identifier(_origin, _blockNum, _logIndex, _time, _source); + Identifier memory id = Identifier(_origin, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics abi.encode(_sender, _message) // data @@ -492,8 +526,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Expect a revert with the MessageDestinationNotRelayChain selector vm.expectRevert(MessageDestinationNotRelayChain.selector); - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, _destination, _target, _nonce), // topics abi.encode(_sender, _message) // data @@ -502,7 +536,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); @@ -529,8 +563,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Call `relayMessage` with CrossL2Inbox as the target to provoke revert. The current chain is the destination // to prevent revert due to invalid destination - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode( L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, Predeploys.CROSS_L2_INBOX, _nonce @@ -541,7 +575,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); @@ -568,8 +602,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Call `relayMessage` with L2ToL2CrossDomainMessenger as the target to provoke revert. The current chain is the // destination to prevent revert due to invalid destination - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode( L2ToL2CrossDomainMessenger.SentMessage.selector, @@ -583,7 +617,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); @@ -623,8 +657,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { _source, _nonce, keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message)) ); - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics abi.encode(_sender, _message) // data @@ -633,7 +667,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); @@ -673,14 +707,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure that the target contract reverts vm.mockCallRevert({ callee: _target, msgValue: _value, data: _message, revertData: abi.encode(false) }); - // Look for correct emitted event - vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - emit L2ToL2CrossDomainMessenger.FailedRelayedMessage( - _source, _nonce, keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message)) - ); - - ICrossL2Inbox.Identifier memory id = - ICrossL2Inbox.Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); + Identifier memory id = + Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics abi.encode(_sender, _message) // data @@ -689,10 +717,12 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Ensure the CrossL2Inbox validates this message vm.mockCall({ callee: Predeploys.CROSS_L2_INBOX, - data: abi.encodeWithSelector(CrossL2Inbox.validateMessage.selector, id, sentMessage), + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), returnData: "" }); + // Expect a revert with the TargetCallFailed selector + vm.expectRevert(TargetCallFailed.selector); hoax(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _value); l2ToL2CrossDomainMessenger.relayMessage{ value: _value }(id, sentMessage); } @@ -744,4 +774,34 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Call `crossDomainMessageSource` to provoke revert l2ToL2CrossDomainMessenger.crossDomainMessageSource(); } + + /// @dev Tests that the `crossDomainMessageContext` function returns the correct value. + function testFuzz_crossDomainMessageContext_succeeds(address _sender, uint256 _source) external { + // Set `entered` to non-zero value to prevent NotEntered revert + l2ToL2CrossDomainMessenger.setEntered(1); + // Ensure that the contract is now entered + assertEq(l2ToL2CrossDomainMessenger.entered(), true); + + // Set cross domain message source in the transient storage + l2ToL2CrossDomainMessenger.setCrossDomainMessageSender(_sender); + l2ToL2CrossDomainMessenger.setCrossDomainMessageSource(_source); + + // Check that the `crossDomainMessageContext` function returns the correct value + (address crossDomainContextSender, uint256 crossDomainContextSource) = + l2ToL2CrossDomainMessenger.crossDomainMessageContext(); + assertEq(crossDomainContextSender, _sender); + assertEq(crossDomainContextSource, _source); + } + + /// @dev Tests that the `crossDomainMessageContext` function reverts when not entered. + function test_crossDomainMessageContext_notEntered_reverts() external { + // Ensure that the contract is not entered + assertEq(l2ToL2CrossDomainMessenger.entered(), false); + + // Expect a revert with the NotEntered selector + vm.expectRevert(NotEntered.selector); + + // Call `crossDomainMessageContext` to provoke revert + l2ToL2CrossDomainMessenger.crossDomainMessageContext(); + } } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 95091c5f0ded1..80ee2e620228b 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -7,20 +7,19 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; + import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC7802 } from "interfaces/L2/IERC7802.sol"; import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; +import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; +import { Preinstalls } from "src/libraries/Preinstalls.sol"; // Target contract -import { - OptimismSuperchainERC20, IOptimismSuperchainERC20Extension, OnlyBridge -} from "src/L2/OptimismSuperchainERC20.sol"; - -// SuperchainERC20 Interfaces -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { IOptimismSuperchainERC20 } from "interfaces/L2/IOptimismSuperchainERC20.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; /// @title OptimismSuperchainERC20Test /// @notice Contract for testing the OptimismSuperchainERC20 contract. @@ -28,22 +27,27 @@ contract OptimismSuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); address internal constant REMOTE_TOKEN = address(0x123); string internal constant NAME = "OptimismSuperchainERC20"; - string internal constant SYMBOL = "SCE"; + string internal constant SYMBOL = "OSC"; uint8 internal constant DECIMALS = 18; - address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; + address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - OptimismSuperchainERC20 public superchainERC20Impl; - OptimismSuperchainERC20 public superchainERC20; + IOptimismSuperchainERC20 public optimismSuperchainERC20Impl; + IOptimismSuperchainERC20 public optimismSuperchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20Impl = new OptimismSuperchainERC20(); + optimismSuperchainERC20Impl = IOptimismSuperchainERC20( + DeployUtils.create1({ + _name: "OptimismSuperchainERC20", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismSuperchainERC20.__constructor__, ())) + }) + ); // Deploy the OptimismSuperchainERC20Beacon contract _deployBeacon(); - superchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); + optimismSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); } /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract @@ -60,9 +64,7 @@ contract OptimismSuperchainERC20Test is Test { EIP1967Helper.setImplementation(_addr, _impl); // Mock implementation address - vm.mockCall( - _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) - ); + vm.mockCall(_impl, abi.encodeCall(IBeacon.implementation, ()), abi.encode(address(optimismSuperchainERC20Impl))); } /// @notice Helper function to deploy a proxy of the OptimismSuperchainERC20 contract. @@ -73,13 +75,13 @@ contract OptimismSuperchainERC20Test is Test { uint8 _decimals ) internal - returns (OptimismSuperchainERC20) + returns (IOptimismSuperchainERC20) { - return OptimismSuperchainERC20( + return IOptimismSuperchainERC20( address( new BeaconProxy( Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON, - abi.encodeCall(OptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) + abi.encodeCall(IOptimismSuperchainERC20.initialize, (_remoteToken, _name, _symbol, _decimals)) ) ) ); @@ -93,14 +95,14 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Test that the contract's `initializer` sets the correct values. function test_initializer_succeeds() public view { - assertEq(superchainERC20.name(), NAME); - assertEq(superchainERC20.symbol(), SYMBOL); - assertEq(superchainERC20.decimals(), DECIMALS); - assertEq(superchainERC20.remoteToken(), REMOTE_TOKEN); + assertEq(optimismSuperchainERC20.name(), NAME); + assertEq(optimismSuperchainERC20.symbol(), SYMBOL); + assertEq(optimismSuperchainERC20.decimals(), DECIMALS); + assertEq(optimismSuperchainERC20.remoteToken(), REMOTE_TOKEN); } /// @notice Tests the `initialize` function reverts when the contract is already initialized. - function testFuzz_initializer_reverts( + function testFuzz_initializer_invalidInitialization_reverts( address _remoteToken, string memory _name, string memory _symbol, @@ -112,30 +114,30 @@ contract OptimismSuperchainERC20Test is Test { vm.expectRevert(Initializable.InvalidInitialization.selector); // Call the `initialize` function again - superchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); + optimismSuperchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); } /// @notice Tests the `mint` function reverts when the caller is not the bridge. function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != BRIDGE); + vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlyBridge` selector - vm.expectRevert(OnlyBridge.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); - superchainERC20.mint(_to, _amount); + optimismSuperchainERC20.mint(_to, _amount); } /// @notice Tests the `mint` function reverts when the amount is zero. function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20.ZeroAddress.selector); // Call the `mint` function with the zero address - vm.prank(BRIDGE); - superchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. @@ -144,47 +146,47 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_to != ZERO_ADDRESS); // Get the total supply and balance of `_to` before the mint to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + uint256 _totalSupplyBefore = IERC20(address(optimismSuperchainERC20)).totalSupply(); + uint256 _toBalanceBefore = IERC20(address(optimismSuperchainERC20)).balanceOf(_to); // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); // Look for the emit of the `Mint` event - vm.expectEmit(address(superchainERC20)); - emit IOptimismSuperchainERC20Extension.Mint(_to, _amount); + vm.expectEmit(address(optimismSuperchainERC20)); + emit IOptimismSuperchainERC20.Mint(_to, _amount); // Call the `mint` function with the bridge caller - vm.prank(BRIDGE); - superchainERC20.mint(_to, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(optimismSuperchainERC20.balanceOf(_to), _toBalanceBefore + _amount); } /// @notice Tests the `burn` function reverts when the caller is not the bridge. function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != BRIDGE); + vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlyBridge` selector - vm.expectRevert(OnlyBridge.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); - superchainERC20.burn(_from, _amount); + optimismSuperchainERC20.burn(_from, _amount); } /// @notice Tests the `burn` function reverts when the amount is zero. function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20.ZeroAddress.selector); // Call the `burn` function with the zero address - vm.prank(BRIDGE); - superchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. @@ -193,198 +195,132 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned - vm.prank(BRIDGE); - superchainERC20.mint(_from, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _fromBalanceBefore = superchainERC20.balanceOf(_from); + uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); + uint256 _fromBalanceBefore = optimismSuperchainERC20.balanceOf(_from); // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); // Look for the emit of the `Burn` event - vm.expectEmit(address(superchainERC20)); - emit IOptimismSuperchainERC20Extension.Burn(_from, _amount); + vm.expectEmit(address(optimismSuperchainERC20)); + emit IOptimismSuperchainERC20.Burn(_from, _amount); // Call the `burn` function with the bridge caller - vm.prank(BRIDGE); - superchainERC20.burn(_from, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.burn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); - } - - /// @notice Tests the `sendERC20` function reverts when the `_to` address is the zero address. - function testFuzz_sendERC20_zeroAddressTo_reverts(uint256 _amount, uint256 _chainId) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - - // Call the `sendERC20` function with the zero address - vm.prank(BRIDGE); - superchainERC20.sendERC20({ _to: ZERO_ADDRESS, _amount: _amount, _chainId: _chainId }); - } - - /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` - /// event. - function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external { - // Ensure `_sender` is not the zero address - vm.assume(_sender != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - - // Mint some tokens to the sender so then they can be sent - vm.prank(BRIDGE); - superchainERC20.mint(_sender, _amount); - - // Get the total supply and balance of `_sender` before the send to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(_sender, ZERO_ADDRESS, _amount); - - // Look for the emit of the `SendERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.SendERC20(_sender, _to, _amount, _chainId); - - // Mock the call over the `sendMessage` function and expect it to be called properly - bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_sender, _to, _amount)); - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector( - IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20), _message - ), - abi.encode("") - ); - - // Call the `sendERC20` function - vm.prank(_sender); - superchainERC20.sendERC20(_to, _amount, _chainId); - - // Check the total supply and balance of `_sender` after the send were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. - function testFuzz_relayERC20_notMessenger_reverts(address _caller, address _to, uint256 _amount) public { - // Ensure the caller is not the messenger - vm.assume(_caller != MESSENGER); - vm.assume(_to != ZERO_ADDRESS); - - // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); - - // Call the `relayERC20` function with the non-messenger caller - vm.prank(_caller); - superchainERC20.relayERC20(_caller, _to, _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not - /// the same SuperchainERC20 address. - function testFuzz_relayERC20_notCrossDomainSender_reverts( - address _crossDomainMessageSender, - address _to, - uint256 _amount - ) - public - { - vm.assume(_to != ZERO_ADDRESS); - vm.assume(_crossDomainMessageSender != address(superchainERC20)); - - // Mock the call over the `crossDomainMessageSender` function setting a wrong sender - vm.mockCall( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(_crossDomainMessageSender) - ); - - // Expect the revert with `InvalidCrossDomainSender` selector - vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); - - // Call the `relayERC20` function with the sender caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_crossDomainMessageSender, _to, _amount); - } - - /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. - function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { - vm.assume(_from != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - - // Mock the call over the `crossDomainMessageSender` function setting the same address as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainERC20)) - ); - - // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(_source) - ); - - // Get the total supply and balance of `_to` before the relay to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - - // Look for the emit of the `RelayERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.RelayERC20(_from, _to, _amount, _source); - - // Call the `relayERC20` function with the messenger caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_from, _to, _amount); - - // Check the total supply and balance of `_to` after the relay were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(optimismSuperchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); } /// @notice Tests the `decimals` function always returns the correct value. function testFuzz_decimals_succeeds(uint8 _decimals) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, _decimals); + IOptimismSuperchainERC20 _newSuperchainERC20 = + _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, _decimals); assertEq(_newSuperchainERC20.decimals(), _decimals); } /// @notice Tests the `REMOTE_TOKEN` function always returns the correct value. function testFuzz_remoteToken_succeeds(address _remoteToken) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, DECIMALS); + IOptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(_remoteToken, NAME, SYMBOL, DECIMALS); assertEq(_newSuperchainERC20.remoteToken(), _remoteToken); } /// @notice Tests the `name` function always returns the correct value. function testFuzz_name_succeeds(string memory _name) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, DECIMALS); + IOptimismSuperchainERC20 _newSuperchainERC20 = + _deploySuperchainERC20Proxy(REMOTE_TOKEN, _name, SYMBOL, DECIMALS); assertEq(_newSuperchainERC20.name(), _name); } /// @notice Tests the `symbol` function always returns the correct value. function testFuzz_symbol_succeeds(string memory _symbol) public { - OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, DECIMALS); + IOptimismSuperchainERC20 _newSuperchainERC20 = + _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, _symbol, DECIMALS); assertEq(_newSuperchainERC20.symbol(), _symbol); } - /// @notice Tests that the `supportsInterface` function returns true for the `IOptimismSuperchainERC20` interface. + /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. function test_supportInterface_succeeds() public view { - assertTrue(superchainERC20.supportsInterface(type(IERC165).interfaceId)); - assertTrue(superchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC165).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC20).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC7802).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20).interfaceId)); } /// @notice Tests that the `supportsInterface` function returns false for any other interface than the - /// `IOptimismSuperchainERC20` one. - function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { + /// `ISuperchainERC20` one. + function testFuzz_supportInterface_returnFalse_works(bytes4 _interfaceId) public view { vm.assume(_interfaceId != type(IERC165).interfaceId); - vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); - assertFalse(superchainERC20.supportsInterface(_interfaceId)); + vm.assume(_interfaceId != type(IERC20).interfaceId); + vm.assume(_interfaceId != type(IERC7802).interfaceId); + vm.assume(_interfaceId != type(IOptimismSuperchainERC20).interfaceId); + assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId)); + } + + /// @notice Tests that the allowance function returns the max uint256 value when the spender is Permit. + /// @param _randomCaller The address that will call the function - used to fuzz better since the behaviour should be + /// the same regardless of the caller. + /// @param _owner The funds owner. + function testFuzz_allowance_fromPermit2_succeeds(address _randomCaller, address _owner) public { + vm.prank(_randomCaller); + uint256 _allowance = optimismSuperchainERC20.allowance(_owner, Preinstalls.Permit2); + + assertEq(_allowance, type(uint256).max); + } + + /// @notice Tests that the allowance function returns the correct allowance when the spender is not Permit. + /// @param _randomCaller The address that will call the function - used to fuzz better + /// since the behaviour should be the same regardless of the caller. + /// @param _owner The funds owner. + /// @param _guy The address of the spender - It cannot be Permit2. + function testFuzz_allowance_succeeds(address _randomCaller, address _owner, address _guy, uint256 _amount) public { + // Assume + vm.assume(_guy != Preinstalls.Permit2); + + // Arrange + vm.prank(_owner); + optimismSuperchainERC20.approve(_guy, _amount); + + // Act + vm.prank(_randomCaller); + uint256 _allowance = optimismSuperchainERC20.allowance(_owner, _guy); + + // Assert + assertEq(_allowance, _amount); + } + + /// @notice Tests that `transferFrom` works when the caller (spender) is Permit2, without any explicit approval. + /// @param _owner The funds owner. + /// @param _recipient The address of the recipient. + /// @param _amount The amount of tokens to transfer. + function testFuzz_transferFrom_whenPermit2IsCaller_succeeds( + address _owner, + address _recipient, + uint256 _amount + ) + public + { + // Arrange + deal(address(optimismSuperchainERC20), _owner, _amount); + + vm.expectEmit(address(optimismSuperchainERC20)); + emit IERC20.Transfer(_owner, _recipient, _amount); + + // Act + vm.prank(Preinstalls.Permit2); + optimismSuperchainERC20.transferFrom(_owner, _recipient, _amount); + + // Assert + assertEq(optimismSuperchainERC20.balanceOf(_recipient), _amount); + // Handle the case where the source and destination are the same to check the source balance. + if (_owner != _recipient) assertEq(optimismSuperchainERC20.balanceOf(_owner), 0); + else assertEq(optimismSuperchainERC20.balanceOf(_owner), _amount); } } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Beacon.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Beacon.t.sol new file mode 100644 index 0000000000000..8a6a770146982 --- /dev/null +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Beacon.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; + +/// @title OptimismSuperchainERC20BeaconTest +/// @notice Contract for testing the OptimismSuperchainERC20Beacon contract. +contract OptimismSuperchainERC20BeaconTest is CommonTest { + /// @notice Sets up the test suite. + function setUp() public override { + super.enableInterop(); + super.setUp(); + } + + /// @notice Test that calling the implementation function returns the correct implementation address. + function test_implementation_isCorrect_works() public view { + IBeacon beacon = IBeacon(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + assertEq(beacon.implementation(), Predeploys.OPTIMISM_SUPERCHAIN_ERC20); + } +} diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol index 3951ebf7452e4..a2f7125fc218e 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -1,53 +1,29 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity 0.8.15; // Testing utilities -import { Test } from "forge-std/Test.sol"; -import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries -import { Predeploys } from "src/libraries/Predeploys.sol"; import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; -import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; // Target contract -import { OptimismSuperchainERC20Factory, OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20Factory.sol"; +import { IOptimismSuperchainERC20 } from "interfaces/L2/IOptimismSuperchainERC20.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; /// @title OptimismSuperchainERC20FactoryTest /// @notice Contract for testing the OptimismSuperchainERC20Factory contract. -contract OptimismSuperchainERC20FactoryTest is Test { +contract OptimismSuperchainERC20FactoryTest is CommonTest { using Bytes32AddressLib for bytes32; - OptimismSuperchainERC20 public superchainERC20Impl; - OptimismSuperchainERC20Factory public superchainERC20Factory; + event OptimismSuperchainERC20Created( + address indexed superchainToken, address indexed remoteToken, address deployer + ); /// @notice Sets up the test suite. - function setUp() public { - superchainERC20Impl = new OptimismSuperchainERC20(); - - // Deploy the OptimismSuperchainERC20Beacon contract - _deployBeacon(); - - superchainERC20Factory = new OptimismSuperchainERC20Factory(); - } - - /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract - function _deployBeacon() internal { - // Deploy the OptimismSuperchainERC20Beacon implementation - address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; - address _impl = Predeploys.predeployToCodeNamespace(_addr); - vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon")); - - // Deploy the ERC1967Proxy contract at the Predeploy - bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy"); - vm.etch(_addr, code); - EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN); - EIP1967Helper.setImplementation(_addr, _impl); - - // Mock implementation address - vm.mockCall( - _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) - ); + function setUp() public override { + super.enableInterop(); + super.setUp(); } /// @notice Test that calling `deploy` with valid parameters succeeds. @@ -62,22 +38,22 @@ contract OptimismSuperchainERC20FactoryTest is Test { { // Arrange bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals)); - address deployment = _calculateTokenAddress(salt, address(superchainERC20Factory)); + address deployment = _calculateTokenAddress(salt, address(l2OptimismSuperchainERC20Factory)); - vm.expectEmit(address(superchainERC20Factory)); - emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); + vm.expectEmit(address(l2OptimismSuperchainERC20Factory)); + emit OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); // Act vm.prank(_caller); - address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + address addr = l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); // Assert assertTrue(addr == deployment); - assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals); - assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); - assertEq(OptimismSuperchainERC20(deployment).name(), _name); - assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol); - assertEq(superchainERC20Factory.deployments(deployment), _remoteToken); + assertTrue(IERC20Metadata(deployment).decimals() == _decimals); + assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); + assertEq(IERC20Metadata(deployment).name(), _name); + assertEq(IERC20Metadata(deployment).symbol(), _symbol); + assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken); } /// @notice Test that calling `deploy` with the same parameters twice reverts. @@ -92,13 +68,13 @@ contract OptimismSuperchainERC20FactoryTest is Test { { // Arrange vm.prank(_caller); - superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); vm.expectRevert(bytes("DEPLOYMENT_FAILED")); // Act vm.prank(_caller); - superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); } /// @notice Precalculates the address of the token contract using CREATE3. diff --git a/packages/contracts-bedrock/test/L2/Predeploys.t.sol b/packages/contracts-bedrock/test/L2/Predeploys.t.sol index 5baa22534ecfe..4fc0a7aa01ddd 100644 --- a/packages/contracts-bedrock/test/L2/Predeploys.t.sol +++ b/packages/contracts-bedrock/test/L2/Predeploys.t.sol @@ -24,17 +24,18 @@ contract PredeploysBaseTest is CommonTest { /// @dev Returns true if the predeploy is initializable. function _isInitializable(address _addr) internal pure returns (bool) { return _addr == Predeploys.L2_CROSS_DOMAIN_MESSENGER || _addr == Predeploys.L2_STANDARD_BRIDGE - || _addr == Predeploys.L2_ERC721_BRIDGE || _addr == Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY; + || _addr == Predeploys.L2_ERC721_BRIDGE || _addr == Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY + || _addr == Predeploys.SOUL_GAS_TOKEN; } /// @dev Returns true if the predeploy uses immutables. function _usesImmutables(address _addr) internal pure returns (bool) { return _addr == Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == Predeploys.SEQUENCER_FEE_WALLET || _addr == Predeploys.BASE_FEE_VAULT || _addr == Predeploys.L1_FEE_VAULT || _addr == Predeploys.EAS - || _addr == Predeploys.GOVERNANCE_TOKEN || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; + || _addr == Predeploys.GOVERNANCE_TOKEN; } - function test_predeployToCodeNamespace() external pure { + function test_predeployToCodeNamespace_works() external pure { assertEq( address(0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000), Predeploys.predeployToCodeNamespace(Predeploys.LEGACY_MESSAGE_PASSER) diff --git a/packages/contracts-bedrock/test/L2/Preinstalls.t.sol b/packages/contracts-bedrock/test/L2/Preinstalls.t.sol index ad14e5ea32fb4..d0ead497834ac 100644 --- a/packages/contracts-bedrock/test/L2/Preinstalls.t.sol +++ b/packages/contracts-bedrock/test/L2/Preinstalls.t.sol @@ -4,12 +4,12 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol"; import { Bytes } from "src/libraries/Bytes.sol"; -import { IEIP712 } from "src/universal/interfaces/IEIP712.sol"; +import { IEIP712 } from "interfaces/universal/IEIP712.sol"; /// @title PreinstallsTest contract PreinstallsTest is CommonTest { /// @dev The domain separator commits to the chainid of the chain - function test_preinstall_permit2_domain_separator() external view { + function test_preinstall_permit2DomainSeparator_works() external view { bytes32 domainSeparator = IEIP712(Preinstalls.Permit2).DOMAIN_SEPARATOR(); bytes32 typeHash = keccak256(abi.encodePacked("EIP712Domain(string name,uint256 chainId,address verifyingContract)")); @@ -23,7 +23,7 @@ contract PreinstallsTest is CommonTest { // Warning the Permit2 domain separator as cached in the DeployPermit2.sol bytecode is incorrect. } - function test_permit2_templating() external pure { + function test_permit2_templating_works() external pure { bytes memory customCode = Preinstalls.getPermit2Code(1234); assertNotEq(customCode.length, 0, "must have code"); assertEq(uint256(bytes32(Bytes.slice(customCode, 6945, 32))), uint256(1234), "expecting custom chain ID"); @@ -118,4 +118,15 @@ contract PreinstallsTest is CommonTest { function test_preinstall_createX_succeeds() external view { assertPreinstall(Preinstalls.CreateX, Preinstalls.CreateXCode); } + + function test_createX_runtimeBytecodeHash_works() external view { + bytes memory createXRuntimeBytecode = Preinstalls.CreateX.code; + bytes32 createXRuntimeBytecodeHash = keccak256(createXRuntimeBytecode); + + assertEq( + createXRuntimeBytecodeHash, + 0xbd8a7ea8cfca7b4e5f5041d7d4b17bc317c5ce42cfbc42066a00cf26b43eb53f, + "CreateX runtime bytecode hash mismatch" + ); + } } diff --git a/packages/contracts-bedrock/test/L2/SequencerFeeVault.t.sol b/packages/contracts-bedrock/test/L2/SequencerFeeVault.t.sol index 2d6ba4e94ec80..dc27fbbad9c9a 100644 --- a/packages/contracts-bedrock/test/L2/SequencerFeeVault.t.sol +++ b/packages/contracts-bedrock/test/L2/SequencerFeeVault.t.sol @@ -7,12 +7,13 @@ import { Reverter } from "test/mocks/Callers.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Contracts -import { SequencerFeeVault } from "src/L2/SequencerFeeVault.sol"; +import { ISequencerFeeVault } from "interfaces/L2/ISequencerFeeVault.sol"; // Libraries import { Hashing } from "src/libraries/Hashing.sol"; import { Types } from "src/libraries/Types.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract SequencerFeeVault_Test is CommonTest { address recipient; @@ -112,11 +113,19 @@ contract SequencerFeeVault_L2Withdrawal_Test is CommonTest { vm.etch( EIP1967Helper.getImplementation(Predeploys.SEQUENCER_FEE_WALLET), address( - new SequencerFeeVault( - deploy.cfg().sequencerFeeVaultRecipient(), - deploy.cfg().sequencerFeeVaultMinimumWithdrawalAmount(), - Types.WithdrawalNetwork.L2 - ) + DeployUtils.create1({ + _name: "SequencerFeeVault", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + ISequencerFeeVault.__constructor__, + ( + deploy.cfg().sequencerFeeVaultRecipient(), + deploy.cfg().sequencerFeeVaultMinimumWithdrawalAmount(), + Types.WithdrawalNetwork.L2 + ) + ) + ) + }) ).code ); diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 66b20c1b911af..788792a4defdf 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -6,57 +6,26 @@ import { Test } from "forge-std/Test.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; // Target contract import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; - -/// @notice Mock contract for the SuperchainERC20 contract so tests can mint tokens. -contract SuperchainERC20Mock is SuperchainERC20 { - string private _name; - string private _symbol; - uint8 private _decimals; - - constructor(string memory __name, string memory __symbol, uint8 __decimals) { - _name = __name; - _symbol = __symbol; - _decimals = __decimals; - } - - function mint(address _account, uint256 _amount) public { - _mint(_account, _amount); - } - - function name() public view virtual override returns (string memory) { - return _name; - } +import { IERC7802, IERC165 } from "interfaces/L2/IERC7802.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ISuperchainERC20 } from "interfaces/L2/ISuperchainERC20.sol"; +import { MockSuperchainERC20Implementation } from "test/mocks/SuperchainERC20Implementation.sol"; - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - function decimals() public view virtual override returns (uint8) { - return _decimals; - } -} /// @title SuperchainERC20Test /// @notice Contract for testing the SuperchainERC20 contract. - contract SuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); - string internal constant NAME = "SuperchainERC20"; - string internal constant SYMBOL = "SCE"; - uint8 internal constant DECIMALS = 18; + address internal constant SUPERCHAIN_TOKEN_BRIDGE = Predeploys.SUPERCHAIN_TOKEN_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - SuperchainERC20 public superchainERC20Impl; - SuperchainERC20Mock public superchainERC20; + SuperchainERC20 public superchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20 = new SuperchainERC20Mock(NAME, SYMBOL, DECIMALS); + superchainERC20 = new MockSuperchainERC20Implementation(); } /// @notice Helper function to setup a mock and expect a call to it. @@ -65,159 +34,101 @@ contract SuperchainERC20Test is Test { vm.expectCall(_receiver, _calldata); } - /// @notice Test that the contract's `constructor` sets the correct values. - function test_constructor_succeeds() public view { - assertEq(superchainERC20.name(), NAME); - assertEq(superchainERC20.symbol(), SYMBOL); - assertEq(superchainERC20.decimals(), DECIMALS); - } + /// @notice Tests the `mint` function reverts when the caller is not the bridge. + function testFuzz_crosschainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); - /// @notice Tests the `sendERC20` function reverts when the `_to` address is the zero address. - function testFuzz_sendERC20_zeroAddressTo_reverts(uint256 _amount, uint256 _chainId) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainERC20.Unauthorized.selector); - // Call the `sendERC20` function with the zero address - superchainERC20.sendERC20({ _to: ZERO_ADDRESS, _amount: _amount, _chainId: _chainId }); + // Call the `mint` function with the non-bridge caller + vm.prank(_caller); + superchainERC20.crosschainMint(_to, _amount); } - /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` - /// event. - function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external { - // Ensure `_sender` is not the zero address - vm.assume(_sender != ZERO_ADDRESS); + /// @notice Tests the `mint` succeeds and emits the `Mint` event. + function testFuzz_crosschainMint_succeeds(address _to, uint256 _amount) public { + // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); - // Mint some tokens to the sender so then they can be sent - superchainERC20.mint(_sender, _amount); - - // Get the total supply and balance of `_sender` before the send to compare later on the assertions + // Get the total supply and balance of `_to` before the mint to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); + uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(_sender, ZERO_ADDRESS, _amount); + emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - // Look for the emit of the `SendERC20` event + // Look for the emit of the `CrosschainMint` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.SendERC20(_sender, _to, _amount, _chainId); - - // Mock the call over the `sendMessage` function and expect it to be called properly - bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_sender, _to, _amount)); - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector( - IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20), _message - ), - abi.encode("") - ); - - // Call the `sendERC20` function - vm.prank(_sender); - superchainERC20.sendERC20(_to, _amount, _chainId); - - // Check the total supply and balance of `_sender` after the send were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); - } + emit IERC7802.CrosschainMint(_to, _amount, SUPERCHAIN_TOKEN_BRIDGE); - /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. - function testFuzz_relayERC20_notMessenger_reverts(address _caller, address _to, uint256 _amount) public { - // Ensure the caller is not the messenger - vm.assume(_caller != MESSENGER); - vm.assume(_to != ZERO_ADDRESS); - - // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); + // Call the `mint` function with the bridge caller + vm.prank(SUPERCHAIN_TOKEN_BRIDGE); + superchainERC20.crosschainMint(_to, _amount); - // Call the `relayERC20` function with the non-messenger caller - vm.prank(_caller); - superchainERC20.relayERC20(_caller, _to, _amount); + // Check the total supply and balance of `_to` after the mint were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); } - /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not - /// the same SuperchainERC20 address. - function testFuzz_relayERC20_notCrossDomainSender_reverts( - address _crossDomainMessageSender, - address _to, - uint256 _amount - ) - public - { - vm.assume(_to != ZERO_ADDRESS); - vm.assume(_crossDomainMessageSender != address(superchainERC20)); - - // Mock the call over the `crossDomainMessageSender` function setting a wrong sender - vm.mockCall( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(_crossDomainMessageSender) - ); + /// @notice Tests the `burn` function reverts when the caller is not the bridge. + function testFuzz_crosschainBurn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); - // Expect the revert with `InvalidCrossDomainSender` selector - vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainERC20.Unauthorized.selector); - // Call the `relayERC20` function with the sender caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_crossDomainMessageSender, _to, _amount); + // Call the `burn` function with the non-bridge caller + vm.prank(_caller); + superchainERC20.crosschainBurn(_from, _amount); } - /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. - function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { + /// @notice Tests the `burn` burns the amount and emits the `CrosschainBurn` event. + function testFuzz_crosschainBurn_succeeds(address _from, uint256 _amount) public { + // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - // Mock the call over the `crossDomainMessageSender` function setting the same address as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainERC20)) - ); - - // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(_source) - ); - - // Get the total supply and balance of `_to` before the relay to compare later on the assertions + // Mint some tokens to `_from` so then they can be burned + vm.prank(SUPERCHAIN_TOKEN_BRIDGE); + superchainERC20.crosschainMint(_from, _amount); + + // Get the total supply and balance of `_from` before the burn to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + uint256 _fromBalanceBefore = superchainERC20.balanceOf(_from); // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); + emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); - // Look for the emit of the `RelayERC20` event + // Look for the emit of the `CrosschainBurn` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.RelayERC20(_from, _to, _amount, _source); - - // Call the `relayERC20` function with the messenger caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_from, _to, _amount); + emit IERC7802.CrosschainBurn(_from, _amount, SUPERCHAIN_TOKEN_BRIDGE); - // Check the total supply and balance of `_to` after the relay were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); - } + // Call the `burn` function with the bridge caller + vm.prank(SUPERCHAIN_TOKEN_BRIDGE); + superchainERC20.crosschainBurn(_from, _amount); - /// @notice Tests the `name` function always returns the correct value. - function testFuzz_name_succeeds(string memory _name) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(_name, SYMBOL, DECIMALS); - assertEq(_newSuperchainERC20.name(), _name); + // Check the total supply and balance of `_from` after the burn were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); } - /// @notice Tests the `symbol` function always returns the correct value. - function testFuzz_symbol_succeeds(string memory _symbol) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(NAME, _symbol, DECIMALS); - assertEq(_newSuperchainERC20.symbol(), _symbol); + /// @notice Tests that the `supportsInterface` function returns true for the `IERC7802` interface. + function test_supportInterface_succeeds() public view { + assertTrue(superchainERC20.supportsInterface(type(IERC165).interfaceId)); + assertTrue(superchainERC20.supportsInterface(type(IERC7802).interfaceId)); + assertTrue(superchainERC20.supportsInterface(type(IERC20).interfaceId)); } - /// @notice Tests the `decimals` function always returns the correct value. - function testFuzz_decimals_succeeds(uint8 _decimals) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(NAME, SYMBOL, _decimals); - assertEq(_newSuperchainERC20.decimals(), _decimals); + /// @notice Tests that the `supportsInterface` function returns false for any other interface than the + /// `IERC7802` one. + function testFuzz_supportInterface_works(bytes4 _interfaceId) public view { + vm.assume(_interfaceId != type(IERC165).interfaceId); + vm.assume(_interfaceId != type(IERC7802).interfaceId); + vm.assume(_interfaceId != type(IERC20).interfaceId); + assertFalse(superchainERC20.supportsInterface(_interfaceId)); } } diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol new file mode 100644 index 0000000000000..2a63961ce4141 --- /dev/null +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { IL2ToL2CrossDomainMessenger } from "interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; + +// Target contract +import { ISuperchainTokenBridge } from "interfaces/L2/ISuperchainTokenBridge.sol"; +import { ISuperchainERC20 } from "interfaces/L2/ISuperchainERC20.sol"; +import { IOptimismSuperchainERC20Factory } from "interfaces/L2/IOptimismSuperchainERC20Factory.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { IERC7802 } from "interfaces/L2/IERC7802.sol"; + +/// @title SuperchainTokenBridgeTest +/// @notice Contract for testing the SuperchainTokenBridge contract. +contract SuperchainTokenBridgeTest is CommonTest { + address internal constant ZERO_ADDRESS = address(0); + string internal constant NAME = "SuperchainERC20"; + string internal constant SYMBOL = "OSE"; + address internal constant REMOTE_TOKEN = address(0x123); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event SendERC20( + address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination + ); + + event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); + + ISuperchainERC20 public superchainERC20; + + /// @notice Sets up the test suite. + function setUp() public override { + super.enableInterop(); + super.setUp(); + + superchainERC20 = ISuperchainERC20( + IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deploy( + REMOTE_TOKEN, NAME, SYMBOL, 18 + ) + ); + } + + /// @notice Helper function to setup a mock and expect a call to it. + function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { + vm.mockCall(_receiver, _calldata, _returned); + vm.expectCall(_receiver, _calldata); + } + + /// @notice Tests the `sendERC20` function reverts when the address `_to` is zero. + function testFuzz_sendERC20_zeroAddressTo_reverts(address _sender, uint256 _amount, uint256 _chainId) public { + // Expect the revert with `ZeroAddress` selector + vm.expectRevert(ISuperchainTokenBridge.ZeroAddress.selector); + + // Call the `sendERC20` function with the zero address as `_to` + vm.prank(_sender); + superchainTokenBridge.sendERC20(address(superchainERC20), ZERO_ADDRESS, _amount, _chainId); + } + + /// @notice Tests the `sendERC20` function reverts when the `token` does not support the IERC7802 interface. + function testFuzz_sendERC20_notSupportedIERC7802_reverts( + address _token, + address _sender, + address _to, + uint256 _amount, + uint256 _chainId + ) + public + { + vm.assume(_to != ZERO_ADDRESS); + assumeAddressIsNot(_token, AddressType.Precompile, AddressType.ForgeAddress); + + // Mock the call over the `supportsInterface` function to return false + vm.mockCall( + _token, abi.encodeCall(ISuperchainERC20.supportsInterface, (type(IERC7802).interfaceId)), abi.encode(false) + ); + + // Expect the revert with `InvalidERC7802` selector + vm.expectRevert(ISuperchainTokenBridge.InvalidERC7802.selector); + + // Call the `sendERC20` function + vm.prank(_sender); + superchainTokenBridge.sendERC20(_token, _to, _amount, _chainId); + } + + /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` + /// event. + function testFuzz_sendERC20_succeeds( + address _sender, + address _to, + uint256 _amount, + uint256 _chainId, + bytes32 _msgHash + ) + external + { + // Ensure `_sender` and `_to` is not the zero address + vm.assume(_sender != ZERO_ADDRESS); + vm.assume(_to != ZERO_ADDRESS); + + // Mint some tokens to the sender so then they can be sent + vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + superchainERC20.crosschainMint(_sender, _amount); + + // Get the total supply and balance of `_sender` before the send to compare later on the assertions + uint256 _totalSupplyBefore = IERC20(address(superchainERC20)).totalSupply(); + uint256 _senderBalanceBefore = IERC20(address(superchainERC20)).balanceOf(_sender); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainERC20)); + emit Transfer(_sender, ZERO_ADDRESS, _amount); + + // Look for the emit of the `SendERC20` event + vm.expectEmit(address(superchainTokenBridge)); + emit SendERC20(address(superchainERC20), _sender, _to, _amount, _chainId); + + // Mock the call over the `sendMessage` function and expect it to be called properly + bytes memory _message = + abi.encodeCall(superchainTokenBridge.relayERC20, (address(superchainERC20), _sender, _to, _amount)); + _mockAndExpect( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeCall( + IL2ToL2CrossDomainMessenger.sendMessage, (_chainId, address(superchainTokenBridge), _message) + ), + abi.encode(_msgHash) + ); + + // Call the `sendERC20` function + vm.prank(_sender); + bytes32 _returnedMsgHash = superchainTokenBridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); + + // Check the message hash was generated correctly + assertEq(_msgHash, _returnedMsgHash); + + // Check the total supply and balance of `_sender` after the send were updated correctly + assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore - _amount); + assertEq(IERC20(address(superchainERC20)).balanceOf(_sender), _senderBalanceBefore - _amount); + } + + /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. + function testFuzz_relayERC20_notMessenger_reverts( + address _token, + address _caller, + address _to, + uint256 _amount + ) + public + { + // Ensure the caller is not the messenger + vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainTokenBridge.Unauthorized.selector); + + // Call the `relayERC20` function with the non-messenger caller + vm.prank(_caller); + superchainTokenBridge.relayERC20(_token, _caller, _to, _amount); + } + + /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not + /// the same SuperchainTokenBridge. + function testFuzz_relayERC20_notCrossDomainSender_reverts( + address _crossDomainMessageSender, + uint256 _source, + address _to, + uint256 _amount + ) + public + { + vm.assume(_crossDomainMessageSender != address(superchainTokenBridge)); + + // Mock the call over the `crossDomainMessageContext` function setting a wrong sender + vm.mockCall( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), + abi.encode(_crossDomainMessageSender, _source) + ); + + // Expect the revert with `InvalidCrossDomainSender` selector + vm.expectRevert(ISuperchainTokenBridge.InvalidCrossDomainSender.selector); + + // Call the `relayERC20` function with the sender caller + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainTokenBridge.relayERC20(address(superchainERC20), _crossDomainMessageSender, _to, _amount); + } + + /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. + function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { + vm.assume(_to != ZERO_ADDRESS); + + // Mock the call over the `crossDomainMessageContext` function setting the same address as value + _mockAndExpect( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), + abi.encode(address(superchainTokenBridge), _source) + ); + + // Get the total supply and balance of `_to` before the relay to compare later on the assertions + uint256 _totalSupplyBefore = IERC20(address(superchainERC20)).totalSupply(); + uint256 _toBalanceBefore = IERC20(address(superchainERC20)).balanceOf(_to); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainERC20)); + emit Transfer(ZERO_ADDRESS, _to, _amount); + + // Look for the emit of the `RelayERC20` event + vm.expectEmit(address(superchainTokenBridge)); + emit RelayERC20(address(superchainERC20), _from, _to, _amount, _source); + + // Call the `relayERC20` function with the messenger caller + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainTokenBridge.relayERC20(address(superchainERC20), _from, _to, _amount); + + // Check the total supply and balance of `_to` after the relay were updated correctly + assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore + _amount); + assertEq(IERC20(address(superchainERC20)).balanceOf(_to), _toBalanceBefore + _amount); + } +} diff --git a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol index 361e982d73249..bc59c76c116fc 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol @@ -6,11 +6,15 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; +import { NotCustomGasToken, Unauthorized, ZeroAddress } from "src/libraries/errors/CommonErrors.sol"; +import { Preinstalls } from "src/libraries/Preinstalls.sol"; // Interfaces -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; +import { IETHLiquidity } from "interfaces/L2/IETHLiquidity.sol"; +import { ISuperchainWETH } from "interfaces/L2/ISuperchainWETH.sol"; +import { IERC7802, IERC165 } from "interfaces/L2/IERC7802.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IL2ToL2CrossDomainMessenger } from "interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; /// @title SuperchainWETH_Test /// @notice Contract for testing the SuperchainWETH contract. @@ -24,11 +28,17 @@ contract SuperchainWETH_Test is CommonTest { /// @notice Emitted when a withdrawal is made. event Withdrawal(address indexed src, uint256 wad); - /// @notice Emitted when an ERC20 is sent. - event SendERC20(address indexed _from, address indexed _to, uint256 _amount, uint256 _chainId); + /// @notice Emitted when a crosschain transfer mints tokens. + event CrosschainMint(address indexed to, uint256 amount, address indexed sender); - /// @notice Emitted when an ERC20 send is relayed. - event RelayERC20(address indexed _from, address indexed _to, uint256 _amount, uint256 _source); + /// @notice Emitted when a crosschain transfer burns tokens. + event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); + + event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination); + + event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source); + + address internal constant ZERO_ADDRESS = address(0); /// @notice Test setup. function setUp() public virtual override { @@ -36,6 +46,12 @@ contract SuperchainWETH_Test is CommonTest { super.setUp(); } + /// @notice Helper function to setup a mock and expect a call to it. + function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { + vm.mockCall(_receiver, _calldata, _returned); + vm.expectCall(_receiver, _calldata); + } + /// @notice Tests that the deposit function can be called on a non-custom gas token chain. /// @param _amount The amount of WETH to send. function testFuzz_deposit_fromNonCustomGasTokenChain_succeeds(uint256 _amount) public { @@ -44,6 +60,7 @@ contract SuperchainWETH_Test is CommonTest { // Arrange vm.deal(alice, _amount); + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false)); // Act vm.expectEmit(address(superchainWeth)); @@ -64,7 +81,7 @@ contract SuperchainWETH_Test is CommonTest { // Arrange vm.deal(address(alice), _amount); - vm.mockCall(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); // Act vm.prank(alice); @@ -86,6 +103,7 @@ contract SuperchainWETH_Test is CommonTest { vm.deal(alice, _amount); vm.prank(alice); superchainWeth.deposit{ value: _amount }(); + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false)); // Act vm.expectEmit(address(superchainWeth)); @@ -108,7 +126,7 @@ contract SuperchainWETH_Test is CommonTest { vm.deal(alice, _amount); vm.prank(alice); superchainWeth.deposit{ value: _amount }(); - vm.mockCall(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); // Act vm.prank(alice); @@ -120,237 +138,549 @@ contract SuperchainWETH_Test is CommonTest { assertEq(superchainWeth.balanceOf(alice), _amount); } - /// @notice Tests that the sendERC20 function always succeeds when called with a sufficient - /// balance no matter the sender, amount, recipient, or chain ID. - /// @param _amount The amount of WETH to send. - /// @param _caller The address of the caller. - /// @param _recipient The address of the recipient. - /// @param _chainId The chain ID to send the WETH to. - function testFuzz_sendERC20_sufficientBalance_succeeds( - uint256 _amount, - address _caller, - address _recipient, - uint256 _chainId - ) - public - { - // Assume - vm.assume(_chainId != block.chainid); - vm.assume(_caller != address(ethLiquidity)); - vm.assume(_caller != address(superchainWeth)); + /// @notice Tests the `crosschainMint` function reverts when the caller is not the `SuperchainTokenBridge`. + function testFuzz_crosschainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainWETH.Unauthorized.selector); + + // Call the `mint` function with the non-bridge caller + vm.prank(_caller); + superchainWeth.crosschainMint(_to, _amount); + } + + /// @notice Tests the `crosschainMint` with non custom gas token succeeds and emits the `CrosschainMint` event. + function testFuzz_crosschainMint_fromBridgeNonCustomGasTokenChain_succeeds(address _to, uint256 _amount) public { + // Ensure `_to` is not the zero address + vm.assume(_to != ZERO_ADDRESS); _amount = bound(_amount, 0, type(uint248).max - 1); - // Arrange - vm.deal(_caller, _amount); + // Get the total supply and balance of `_to` before the mint to compare later on the assertions + uint256 _totalSupplyBefore = superchainWeth.totalSupply(); + uint256 _toBalanceBefore = superchainWeth.balanceOf(_to); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainWeth)); + emit Transfer(ZERO_ADDRESS, _to, _amount); + + // Look for the emit of the `CrosschainMint` event + vm.expectEmit(address(superchainWeth)); + emit CrosschainMint(_to, _amount, Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + + // Mock the `isCustomGasToken` function to return false + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false)); + + // Expect the call to the `mint` function in the `ETHLiquidity` contract + vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 1); + + // Call the `mint` function with the bridge caller + vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + superchainWeth.crosschainMint(_to, _amount); + + // Check the total supply and balance of `_to` after the mint were updated correctly + assertEq(superchainWeth.totalSupply(), _totalSupplyBefore + _amount); + assertEq(superchainWeth.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(address(superchainWeth).balance, _amount); + } + + /// @notice Tests the `crosschainMint` with custom gas token succeeds and emits the `CrosschainMint` event. + function testFuzz_crosschainMint_fromBridgeCustomGasTokenChain_succeeds(address _to, uint256 _amount) public { + // Ensure `_to` is not the zero address + vm.assume(_to != ZERO_ADDRESS); + _amount = bound(_amount, 0, type(uint248).max - 1); + + // Get the balance of `_to` before the mint to compare later on the assertions + uint256 _toBalanceBefore = superchainWeth.balanceOf(_to); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainWeth)); + emit Transfer(ZERO_ADDRESS, _to, _amount); + + // Look for the emit of the `CrosschainMint` event + vm.expectEmit(address(superchainWeth)); + emit CrosschainMint(_to, _amount, Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + + // Mock the `isCustomGasToken` function to return false + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); + + // Expect to not call the `mint` function in the `ETHLiquidity` contract + vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 0); + + // Call the `mint` function with the bridge caller + vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + superchainWeth.crosschainMint(_to, _amount); + + // Check the total supply and balance of `_to` after the mint were updated correctly + assertEq(superchainWeth.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(superchainWeth.totalSupply(), 0); + assertEq(address(superchainWeth).balance, 0); + } + + /// @notice Tests the `crosschainBurn` function reverts when the caller is not the `SuperchainTokenBridge`. + function testFuzz_crosschainBurn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainWETH.Unauthorized.selector); + + // Call the `burn` function with the non-bridge caller vm.prank(_caller); + superchainWeth.crosschainBurn(_from, _amount); + } + + /// @notice Tests the `crosschainBurn` with non custom gas token burns the amount and emits the `CrosschainBurn` + /// event. + function testFuzz_crosschainBurn_fromBridgeNonCustomGasTokenChain_succeeds(address _from, uint256 _amount) public { + // Ensure `_from` is not the zero address + vm.assume(_from != ZERO_ADDRESS); + _amount = bound(_amount, 0, type(uint248).max - 1); + + // Deposit some tokens to `_from` so then they can be burned + vm.deal(_from, _amount); + vm.prank(_from); superchainWeth.deposit{ value: _amount }(); - // Act + // Get the total supply and balance of `_from` before the burn to compare later on the assertions + uint256 _totalSupplyBefore = superchainWeth.totalSupply(); + uint256 _fromBalanceBefore = superchainWeth.balanceOf(_from); + + // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainWeth)); - emit Transfer(_caller, address(0), _amount); + emit Transfer(_from, ZERO_ADDRESS, _amount); + + // Look for the emit of the `CrosschainBurn` event vm.expectEmit(address(superchainWeth)); - emit SendERC20(_caller, _recipient, _amount, _chainId); + emit CrosschainBurn(_from, _amount, Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + + // Mock the `isCustomGasToken` function to return false + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false)); + + // Expect the call to the `burn` function in the `ETHLiquidity` contract vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.burn, ()), 1); - vm.expectCall( - Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall( - IL2ToL2CrossDomainMessenger.sendMessage, - ( - _chainId, - address(superchainWeth), - abi.encodeCall(superchainWeth.relayERC20, (_caller, _recipient, _amount)) - ) - ), - 1 - ); - vm.prank(_caller); - superchainWeth.sendERC20(_recipient, _amount, _chainId); - // Assert - assertEq(_caller.balance, 0); - assertEq(superchainWeth.balanceOf(_caller), 0); + // Call the `burn` function with the bridge caller + vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + superchainWeth.crosschainBurn(_from, _amount); + + // Check the total supply and balance of `_from` after the burn were updated correctly + assertEq(superchainWeth.totalSupply(), _totalSupplyBefore - _amount); + assertEq(superchainWeth.balanceOf(_from), _fromBalanceBefore - _amount); + assertEq(address(superchainWeth).balance, 0); } - /// @notice Tests that the sendERC20 function can be called with a sufficient balance on a - /// custom gas token chain. Also tests that the proper calls are made and the proper - /// events are emitted but ETH is not burned via the ETHLiquidity contract. - /// @param _amount The amount of WETH to send. - /// @param _chainId The chain ID to send the WETH to. - function testFuzz_sendERC20_sufficientFromCustomGasTokenChain_succeeds(uint256 _amount, uint256 _chainId) public { - // Assume - vm.assume(_chainId != block.chainid); + /// @notice Tests the `crosschainBurn` with custom gas token burns the amount and emits the `CrosschainBurn` + /// event. + function testFuzz_crosschainBurn_fromBridgeCustomGasTokenChain_succeeds(address _from, uint256 _amount) public { + // Ensure `_from` is not the zero address + vm.assume(_from != ZERO_ADDRESS); _amount = bound(_amount, 0, type(uint248).max - 1); - // Arrange - vm.deal(alice, _amount); - vm.prank(alice); - superchainWeth.deposit{ value: _amount }(); - vm.mockCall(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); + // Mock the `isCustomGasToken` function to return false + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); - // Act + // Mint some tokens to `_from` so then they can be burned + vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + superchainWeth.crosschainMint(_from, _amount); + + // Get the total supply and balance of `_from` before the burn to compare later on the assertions + uint256 _totalSupplyBefore = superchainWeth.totalSupply(); + uint256 _fromBalanceBefore = superchainWeth.balanceOf(_from); + + // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainWeth)); - emit Transfer(alice, address(0), _amount); + emit Transfer(_from, ZERO_ADDRESS, _amount); + + // Look for the emit of the `CrosschainBurn` event vm.expectEmit(address(superchainWeth)); - emit SendERC20(alice, bob, _amount, _chainId); + emit CrosschainBurn(_from, _amount, Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + + // Expect to not call the `burn` function in the `ETHLiquidity` contract vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.burn, ()), 0); - vm.expectCall( - Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall( - IL2ToL2CrossDomainMessenger.sendMessage, - (_chainId, address(superchainWeth), abi.encodeCall(superchainWeth.relayERC20, (alice, bob, _amount))) - ), - 1 - ); - vm.prank(alice); - superchainWeth.sendERC20(bob, _amount, _chainId); - // Assert - assertEq(alice.balance, 0); - assertEq(superchainWeth.balanceOf(alice), 0); + // Call the `burn` function with the bridge caller + vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + superchainWeth.crosschainBurn(_from, _amount); + + // Check the total supply and balance of `_from` after the burn were updated correctly + assertEq(superchainWeth.balanceOf(_from), _fromBalanceBefore - _amount); + assertEq(superchainWeth.totalSupply(), _totalSupplyBefore); + assertEq(address(superchainWeth).balance, 0); } - /// @notice Tests that the sendERC20 function reverts when called with insufficient balance. - /// @param _amount The amount of WETH to send. - /// @param _chainId The chain ID to send the WETH to. - function testFuzz_sendERC20_insufficientBalance_fails(uint256 _amount, uint256 _chainId) public { + /// @notice Tests that the `crosschainBurn` function reverts when called with insufficient balance. + function testFuzz_crosschainBurn_insufficientBalance_fails(address _from, uint256 _amount) public { // Assume - vm.assume(_chainId != block.chainid); + vm.assume(_from != ZERO_ADDRESS); _amount = bound(_amount, 0, type(uint248).max - 1); // Arrange - vm.deal(alice, _amount); - vm.prank(alice); + vm.deal(_from, _amount); + vm.prank(_from); superchainWeth.deposit{ value: _amount }(); // Act - vm.expectRevert(); - superchainWeth.sendERC20(bob, _amount + 1, _chainId); + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args + superchainWeth.crosschainBurn(_from, _amount + 1); + } + + /// @notice Test that the internal mint function reverts to protect against accidentally changing the visibility. + function testFuzz_calling_internalMintFunction_reverts(address _caller, address _to, uint256 _amount) public { + // Arrange + // nosemgrep: sol-style-use-abi-encodecall + bytes memory _calldata = abi.encodeWithSignature("_mint(address,uint256)", _to, _amount); + vm.expectRevert(bytes("")); + + // Act + vm.prank(_caller); + (bool success,) = address(superchainWeth).call(_calldata); // Assert - assertEq(alice.balance, 0); - assertEq(superchainWeth.balanceOf(alice), _amount); + assertFalse(success); } - /// @notice Tests that the relayERC20 function can be called from the - /// L2ToL2CrossDomainMessenger as long as the crossDomainMessageSender is the - /// SuperchainWETH contract. - /// @param _amount The amount of WETH to send. - function testFuzz_relayERC20_fromMessenger_succeeds(address _sender, uint256 _amount, uint256 _chainId) public { + /// @notice Test that the mint function reverts to protect against accidentally changing the visibility. + function testFuzz_calling_mintFunction_reverts(address _caller, address _to, uint256 _amount) public { + // Arrange + // nosemgrep: sol-style-use-abi-encodecall + bytes memory _calldata = abi.encodeWithSignature("mint(address,uint256)", _to, _amount); + vm.expectRevert(bytes("")); + + // Act + vm.prank(_caller); + (bool success,) = address(superchainWeth).call(_calldata); + + // Assert + assertFalse(success); + } + + /// @notice Test that the internal burn function reverts to protect against accidentally changing the visibility. + function testFuzz_calling_internalBurnFunction_reverts(address _caller, address _from, uint256 _amount) public { + // Arrange + // nosemgrep: sol-style-use-abi-encodecall + bytes memory _calldata = abi.encodeWithSignature("_burn(address,uint256)", _from, _amount); + vm.expectRevert(bytes("")); + + // Act + vm.prank(_caller); + (bool success,) = address(superchainWeth).call(_calldata); + + // Assert + assertFalse(success); + } + + /// @notice Test that the burn function reverts to protect against accidentally changing the visibility. + function testFuzz_calling_burnFuunction_reverts(address _caller, address _from, uint256 _amount) public { + // Arrange + // nosemgrep: sol-style-use-abi-encodecall + bytes memory _calldata = abi.encodeWithSignature("burn(address,uint256)", _from, _amount); + vm.expectRevert(bytes("")); + + // Act + vm.prank(_caller); + (bool success,) = address(superchainWeth).call(_calldata); + + // Assert + assertFalse(success); + } + + /// @notice Tests that the allowance function returns the max uint256 value when the spender is Permit. + /// @param _randomCaller The address that will call the function - used to fuzz better since the behaviour should be + /// the same regardless of the caller. + /// @param _src The funds owner. + function testFuzz_allowance_fromPermit2_succeeds(address _randomCaller, address _src) public { + vm.prank(_randomCaller); + uint256 _allowance = superchainWeth.allowance(_src, Preinstalls.Permit2); + + assertEq(_allowance, type(uint256).max); + } + + /// @notice Tests that the allowance function returns the correct allowance when the spender is not Permit. + /// @param _randomCaller The address that will call the function - used to fuzz better + /// since the behaviour should be the same regardless of the caller. + /// @param _src The funds owner. + /// @param _guy The address of the spender - It cannot be Permit2. + function testFuzz_allowance_succeeds(address _randomCaller, address _src, address _guy, uint256 _wad) public { // Assume - vm.assume(_chainId != block.chainid); - vm.assume(_sender != address(ethLiquidity)); - vm.assume(_sender != address(superchainWeth)); - _amount = bound(_amount, 0, type(uint248).max - 1); + vm.assume(_guy != Preinstalls.Permit2); // Arrange - vm.mockCall( - Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSender, ()), - abi.encode(address(superchainWeth)) - ); - vm.mockCall( - Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSource, ()), - abi.encode(_chainId) - ); + vm.prank(_src); + superchainWeth.approve(_guy, _wad); // Act + vm.prank(_randomCaller); + uint256 _allowance = superchainWeth.allowance(_src, _guy); + + // Assert + assertEq(_allowance, _wad); + } + + /// @notice Tests that `transferFrom` works when the caller (spender) is Permit2, without any explicit approval. + /// @param _src The funds owner. + /// @param _dst The address of the recipient. + /// @param _wad The amount of WETH to transfer. + function testFuzz_transferFrom_whenPermit2IsCaller_succeeds(address _src, address _dst, uint256 _wad) public { + vm.assume(_src != _dst); + + // Arrange + deal(address(superchainWeth), _src, _wad); + vm.expectEmit(address(superchainWeth)); - emit RelayERC20(_sender, bob, _amount, _chainId); - vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 1); - vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - superchainWeth.relayERC20(_sender, bob, _amount); + emit Transfer(_src, _dst, _wad); + + // Act + vm.prank(Preinstalls.Permit2); + superchainWeth.transferFrom(_src, _dst, _wad); // Assert - assertEq(address(superchainWeth).balance, _amount); - assertEq(superchainWeth.balanceOf(bob), _amount); + assertEq(superchainWeth.balanceOf(_src), 0); + assertEq(superchainWeth.balanceOf(_dst), _wad); } - /// @notice Tests that the relayERC20 function can be called from the - /// L2ToL2CrossDomainMessenger as long as the crossDomainMessageSender is the - /// SuperchainWETH contract, even when the chain is a custom gas token chain. Shows - /// that ETH is not minted in this case but the SuperchainWETH balance is updated. - /// @param _amount The amount of WETH to send. - function testFuzz_relayERC20_fromMessengerCustomGasTokenChain_succeeds( + /// @notice Tests that `transferFrom` works when the caller (spender) is Permit2, and `_src` equals `_dst` without + /// an explicit approval. + /// The balance should remain the same on this scenario. + /// @param _user The source and destination address. + /// @param _wad The amount of WETH to transfer. + function testFuzz_transferFrom_whenPermit2IsCallerAndSourceIsDestination_succeeds( + address _user, + uint256 _wad + ) + public + { + // Arrange + deal(address(superchainWeth), _user, _wad); + + vm.expectEmit(address(superchainWeth)); + emit Transfer(_user, _user, _wad); + + // Act + vm.prank(Preinstalls.Permit2); + superchainWeth.transferFrom(_user, _user, _wad); + + // Assert + assertEq(superchainWeth.balanceOf(_user), _wad); + } + + /// @notice Tests that the `supportsInterface` function returns true for the `IERC7802` interface. + function test_supportInterface_succeeds() public view { + assertTrue(superchainWeth.supportsInterface(type(IERC165).interfaceId)); + assertTrue(superchainWeth.supportsInterface(type(IERC7802).interfaceId)); + assertTrue(superchainWeth.supportsInterface(type(IERC20).interfaceId)); + } + + /// @notice Tests that the `supportsInterface` function returns false for any other interface than the + /// `IERC7802` one. + function testFuzz_supportInterface_works(bytes4 _interfaceId) public view { + vm.assume(_interfaceId != type(IERC165).interfaceId); + vm.assume(_interfaceId != type(IERC7802).interfaceId); + vm.assume(_interfaceId != type(IERC20).interfaceId); + assertFalse(superchainWeth.supportsInterface(_interfaceId)); + } + + /// @notice Tests the `sendETH` function reverts when the address `_to` is zero. + function testFuzz_sendETH_zeroAddressTo_reverts(address _sender, uint256 _amount, uint256 _chainId) public { + // Expect the revert with `ZeroAddress` selector + vm.expectRevert(ZeroAddress.selector); + + vm.deal(_sender, _amount); + vm.prank(_sender); + // Call the `sendETH` function with the zero address as `_to` + superchainWeth.sendETH{ value: _amount }(ZERO_ADDRESS, _chainId); + } + + /// @notice Tests the `sendETH` function burns the sender ETH, sends the message, and emits the `SendETH` + /// event. + function testFuzz_sendETH_fromNonCustomGasTokenChain_succeeds( address _sender, + address _to, uint256 _amount, - uint256 _chainId + uint256 _chainId, + bytes32 _msgHash ) - public + external { // Assume - vm.assume(_chainId != block.chainid); vm.assume(_sender != address(ethLiquidity)); - vm.assume(_sender != address(superchainWeth)); + vm.assume(_sender != ZERO_ADDRESS); + vm.assume(_to != ZERO_ADDRESS); _amount = bound(_amount, 0, type(uint248).max - 1); // Arrange - vm.mockCall( + vm.deal(_sender, _amount); + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false)); + + // Get the total balance of `_sender` before the send to compare later on the assertions + uint256 _senderBalanceBefore = _sender.balance; + + // Look for the emit of the `SendETH` event + vm.expectEmit(address(superchainWeth)); + emit SendETH(_sender, _to, _amount, _chainId); + + // Expect the call to the `burn` function in the `ETHLiquidity` contract + vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.burn, ()), 1); + + // Mock the call over the `sendMessage` function and expect it to be called properly + bytes memory _message = abi.encodeCall(superchainWeth.relayETH, (_sender, _to, _amount)); + _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSender, ()), - abi.encode(address(superchainWeth)) + abi.encodeCall(IL2ToL2CrossDomainMessenger.sendMessage, (_chainId, address(superchainWeth), _message)), + abi.encode(_msgHash) ); + + // Call the `sendETH` function + vm.prank(_sender); + bytes32 _returnedMsgHash = superchainWeth.sendETH{ value: _amount }(_to, _chainId); + + // Check the message hash was generated correctly + assertEq(_msgHash, _returnedMsgHash); + + // Check the total supply and balance of `_sender` after the send were updated correctly + assertEq(_sender.balance, _senderBalanceBefore - _amount); + } + + /// @notice Tests the `sendETH` function reverts when called on a custom gas token chain. + function testFuzz_sendETH_fromCustomGasTokenChain_fails( + address _sender, + address _to, + uint256 _amount, + uint256 _chainId + ) + external + { + // Assume + vm.assume(_sender != ZERO_ADDRESS); + vm.assume(_to != ZERO_ADDRESS); + _amount = bound(_amount, 0, type(uint248).max - 1); + + // Arrange + vm.deal(_sender, _amount); + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); + + // Call the `sendETH` function + vm.prank(_sender); + vm.expectRevert(NotCustomGasToken.selector); + superchainWeth.sendETH{ value: _amount }(_to, _chainId); + } + + /// @notice Tests the `relayETH` function reverts when the caller is not the L2ToL2CrossDomainMessenger. + function testFuzz_relayETH_notMessenger_reverts(address _caller, address _to, uint256 _amount) public { + // Ensure the caller is not the messenger + vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); + + // Call the `relayETH` function with the non-messenger caller + vm.prank(_caller); + superchainWeth.relayETH(_caller, _to, _amount); + } + + /// @notice Tests the `relayETH` function reverts when the `crossDomainMessageSender` that sent the message is not + /// the same SuperchainWETH. + function testFuzz_relayETH_notCrossDomainSender_reverts( + address _crossDomainMessageSender, + uint256 _source, + address _to, + uint256 _amount + ) + public + { + vm.assume(_crossDomainMessageSender != address(superchainWeth)); + + // Mock the call over the `crossDomainMessageContext` function setting a wrong sender vm.mockCall( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSource, ()), - abi.encode(_chainId) + abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), + abi.encode(_crossDomainMessageSender, _source) ); - vm.mockCall(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); - // Act - vm.expectEmit(address(superchainWeth)); - emit RelayERC20(_sender, bob, _amount, _chainId); - vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 0); - vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - superchainWeth.relayERC20(_sender, bob, _amount); + // Expect the revert with `InvalidCrossDomainSender` selector + vm.expectRevert(ISuperchainWETH.InvalidCrossDomainSender.selector); - // Assert - assertEq(address(superchainWeth).balance, 0); - assertEq(superchainWeth.balanceOf(bob), _amount); + // Call the `relayETH` function with the sender caller + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainWeth.relayETH(_crossDomainMessageSender, _to, _amount); } - /// @notice Tests that the relayERC20 function reverts when not called from the - /// L2ToL2CrossDomainMessenger. - /// @param _amount The amount of WETH to send. - function testFuzz_relayERC20_notFromMessenger_fails(address _sender, uint256 _amount) public { + /// @notice Tests the `relayETH` function succeeds and sends SuperchainWETH to the recipient on a custom gas token + /// chain. + function testFuzz_relayETH_fromCustomGasTokenChain_succeeds( + address _from, + address _to, + uint256 _amount, + uint256 _source + ) + public + { // Assume + vm.assume(_to != ZERO_ADDRESS); _amount = bound(_amount, 0, type(uint248).max - 1); + // Get the balance of `_to` before the mint to compare later on the assertions + uint256 _toBalanceBefore = superchainWeth.balanceOf(_to); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainWeth)); + emit Transfer(ZERO_ADDRESS, _to, _amount); + + // Look for the emit of the `RelayETH` event + vm.expectEmit(address(superchainWeth)); + emit RelayETH(_from, _to, _amount, _source); + // Arrange - // Nothing to arrange. + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true)); + _mockAndExpect( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), + abi.encode(address(superchainWeth), _source) + ); + // Expect to not call the `mint` function in the `ETHLiquidity` contract + vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 0); // Act - vm.expectRevert(Unauthorized.selector); - vm.prank(alice); - superchainWeth.relayERC20(_sender, bob, _amount); + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainWeth.relayETH(_from, _to, _amount); - // Assert + // Check the total supply and balance of `_to` after the mint were updated correctly + assertEq(superchainWeth.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(superchainWeth.totalSupply(), 0); assertEq(address(superchainWeth).balance, 0); - assertEq(superchainWeth.balanceOf(bob), 0); } - /// @notice Tests that the relayERC20 function reverts when called from the - /// L2ToL2CrossDomainMessenger but the crossDomainMessageSender is not the - /// SuperchainWETH contract. - /// @param _amount The amount of WETH to send. - function testFuzz_relayERC20_fromMessengerNotFromSuperchainWETH_fails(address _sender, uint256 _amount) public { + /// @notice Tests the `relayETH` function relays the proper amount of ETH and emits the `RelayETH` event. + function testFuzz_relayETH_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { // Assume + vm.assume(_to != ZERO_ADDRESS); + assumePayable(_to); _amount = bound(_amount, 0, type(uint248).max - 1); // Arrange - vm.mockCall( + vm.deal(address(superchainWeth), _amount); + vm.deal(Predeploys.ETH_LIQUIDITY, _amount); + _mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false)); + _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSender, ()), - abi.encode(address(alice)) + abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), + abi.encode(address(superchainWeth), _source) ); - // Act - vm.expectRevert(Unauthorized.selector); + uint256 _toBalanceBefore = _to.balance; + + // Look for the emit of the `RelayETH` event + vm.expectEmit(address(superchainWeth)); + emit RelayETH(_from, _to, _amount, _source); + + // Expect the call to the `mint` function in the `ETHLiquidity` contract + vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 1); + + // Call the `RelayETH` function with the messenger caller vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - superchainWeth.relayERC20(_sender, bob, _amount); + superchainWeth.relayETH(_from, _to, _amount); - // Assert - assertEq(address(superchainWeth).balance, 0); - assertEq(superchainWeth.balanceOf(bob), 0); + assertEq(_to.balance, _toBalanceBefore + _amount); } } diff --git a/packages/contracts-bedrock/test/L2/WETH.t.sol b/packages/contracts-bedrock/test/L2/WETH.t.sol index fa16be1f53cce..84bb138d74fba 100644 --- a/packages/contracts-bedrock/test/L2/WETH.t.sol +++ b/packages/contracts-bedrock/test/L2/WETH.t.sol @@ -10,7 +10,7 @@ import { WETH } from "src/L2/WETH.sol"; contract WETH_Test is CommonTest { /// @dev Tests that the name function returns the correct value. function testFuzz_name_succeeds(string memory _gasPayingTokenName) external { - vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingTokenName()"), abi.encode(_gasPayingTokenName)); + vm.mockCall(address(l1Block), abi.encodeCall(l1Block.gasPayingTokenName, ()), abi.encode(_gasPayingTokenName)); assertEq(string.concat("Wrapped ", _gasPayingTokenName), weth.name()); } @@ -18,7 +18,7 @@ contract WETH_Test is CommonTest { /// @dev Tests that the symbol function returns the correct value. function testFuzz_symbol_succeeds(string memory _gasPayingTokenSymbol) external { vm.mockCall( - address(l1Block), abi.encodeWithSignature("gasPayingTokenSymbol()"), abi.encode(_gasPayingTokenSymbol) + address(l1Block), abi.encodeCall(l1Block.gasPayingTokenSymbol, ()), abi.encode(_gasPayingTokenSymbol) ); assertEq(string.concat("W", _gasPayingTokenSymbol), weth.symbol()); diff --git a/packages/contracts-bedrock/test/actors/FaultDisputeActors.sol b/packages/contracts-bedrock/test/actors/FaultDisputeActors.sol index 20f18eddc3c00..b2e7a1d64d188 100644 --- a/packages/contracts-bedrock/test/actors/FaultDisputeActors.sol +++ b/packages/contracts-bedrock/test/actors/FaultDisputeActors.sol @@ -8,7 +8,7 @@ import { CommonBase } from "forge-std/Base.sol"; import "src/dispute/lib/Types.sol"; // Interfaces -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; /// @title GameSolver /// @notice The `GameSolver` contract is a contract that can produce an array of available diff --git a/packages/contracts-bedrock/test/cannon/MIPS.t.sol b/packages/contracts-bedrock/test/cannon/MIPS.t.sol index 998bc4d4aa794..aa9830981216a 100644 --- a/packages/contracts-bedrock/test/cannon/MIPS.t.sol +++ b/packages/contracts-bedrock/test/cannon/MIPS.t.sol @@ -4,9 +4,8 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; -// Contracts -import { MIPS } from "src/cannon/MIPS.sol"; -import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; +// Scripts +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Libraries import { MIPSInstructions } from "src/cannon/libraries/MIPSInstructions.sol"; @@ -15,16 +14,27 @@ import { InvalidExitedValue, InvalidMemoryProof } from "src/cannon/libraries/Can import "src/dispute/lib/Types.sol"; // Interfaces -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { IMIPS } from "interfaces/cannon/IMIPS.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; contract MIPS_Test is CommonTest { - MIPS internal mips; - PreimageOracle internal oracle; + IMIPS internal mips; + IPreimageOracle internal oracle; function setUp() public virtual override { super.setUp(); - oracle = new PreimageOracle(0, 0); - mips = new MIPS(IPreimageOracle(address(oracle))); + oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ); + mips = IMIPS( + DeployUtils.create1({ + _name: "MIPS", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS.__constructor__, (oracle))) + }) + ); vm.store(address(mips), 0x0, bytes32(abi.encode(address(oracle)))); vm.label(address(oracle), "PreimageOracle"); vm.label(address(mips), "MIPS"); @@ -53,7 +63,7 @@ contract MIPS_Test is CommonTest { function test_step_abi_succeeds() external { uint32[32] memory registers; registers[16] = 0xbfff0000; - MIPS.State memory state = MIPS.State({ + IMIPS.State memory state = IMIPS.State({ memRoot: hex"30be14bdf94d7a93989a6263f1e116943dc052d584730cae844bf330dfddce2f", preimageKey: bytes32(0), preimageOffset: 0, @@ -82,7 +92,7 @@ contract MIPS_Test is CommonTest { // Rest of this stuff doesn't matter very much, just setting up some state to edit. // Here just using the parameters for the ADD test below. uint32 insn = encodespec(17, 18, 8, 0x20); - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); // Compute the encoded state and manipulate it. bytes memory enc = encodeState(state); @@ -102,12 +112,12 @@ contract MIPS_Test is CommonTest { function test_add_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x20); // add t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 12; state.registers[18] = 20; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -122,12 +132,12 @@ contract MIPS_Test is CommonTest { function test_addu_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x21); // addu t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 12; state.registers[18] = 20; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -143,12 +153,12 @@ contract MIPS_Test is CommonTest { function test_addi_succeeds() external { uint16 imm = 40; uint32 insn = encodeitype(0x8, 17, 8, imm); // addi t0, s1, 40 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 1; // t0 state.registers[17] = 4; // s1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -163,12 +173,12 @@ contract MIPS_Test is CommonTest { function test_addiSign_succeeds() external { uint16 imm = 0xfffe; // -2 uint32 insn = encodeitype(0x8, 17, 8, imm); // addi t0, s1, 40 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 1; // t0 state.registers[17] = 2; // s1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -183,12 +193,12 @@ contract MIPS_Test is CommonTest { function test_addui_succeeds() external { uint16 imm = 40; uint32 insn = encodeitype(0x9, 17, 8, imm); // addui t0, s1, 40 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 1; // t0 state.registers[17] = 4; // s1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -202,12 +212,12 @@ contract MIPS_Test is CommonTest { function test_sub_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x22); // sub t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 20; state.registers[18] = 12; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -222,12 +232,12 @@ contract MIPS_Test is CommonTest { function test_subu_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x23); // subu t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 20; state.registers[18] = 12; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -242,12 +252,12 @@ contract MIPS_Test is CommonTest { function test_and_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x24); // and t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 1200; state.registers[18] = 490; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -263,12 +273,12 @@ contract MIPS_Test is CommonTest { function test_andi_succeeds() external { uint16 imm = 40; uint32 insn = encodeitype(0xc, 17, 8, imm); // andi t0, s1, 40 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 1; // t0 state.registers[17] = 4; // s1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -282,12 +292,12 @@ contract MIPS_Test is CommonTest { function test_or_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x25); // or t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 1200; state.registers[18] = 490; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -303,12 +313,12 @@ contract MIPS_Test is CommonTest { function test_ori_succeeds() external { uint16 imm = 40; uint32 insn = encodeitype(0xd, 17, 8, imm); // ori t0, s1, 40 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 1; // t0 state.registers[17] = 4; // s1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -322,12 +332,12 @@ contract MIPS_Test is CommonTest { function test_xor_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x26); // xor t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 1200; state.registers[18] = 490; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -343,12 +353,12 @@ contract MIPS_Test is CommonTest { function test_xori_succeeds() external { uint16 imm = 40; uint32 insn = encodeitype(0xe, 17, 8, imm); // xori t0, s1, 40 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 1; // t0 state.registers[17] = 4; // s1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -362,12 +372,12 @@ contract MIPS_Test is CommonTest { function test_nor_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x27); // nor t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 1200; state.registers[18] = 490; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -382,11 +392,11 @@ contract MIPS_Test is CommonTest { function test_slt_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x2a); // slt t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 0xFF_FF_FF_FE; // -2 state.registers[18] = 5; - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -411,12 +421,12 @@ contract MIPS_Test is CommonTest { function test_sltu_succeeds() external { uint32 insn = encodespec(17, 18, 8, 0x2b); // sltu t0, s1, s2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[17] = 1200; state.registers[18] = 490; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -432,12 +442,12 @@ contract MIPS_Test is CommonTest { function test_lb_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x20, 0x9, 0x8, 0x4); // lb $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_00_00_00); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_00_00_00); state.registers[8] = 0; // t0 state.registers[9] = t1; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -454,12 +464,12 @@ contract MIPS_Test is CommonTest { uint32 val = 0x12_23_00_00; uint32 insn = encodeitype(0x21, 0x9, 0x8, 0x4); // lh $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, val); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, val); state.registers[8] = 0; // t0 state.registers[9] = t1; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -476,12 +486,12 @@ contract MIPS_Test is CommonTest { uint32 val = 0x12_23_45_67; uint32 insn = encodeitype(0x23, 0x9, 0x8, 0x4); // lw $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, val); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, val); state.registers[8] = 0; // t0 state.registers[9] = t1; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -496,12 +506,12 @@ contract MIPS_Test is CommonTest { function test_lbu_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x24, 0x9, 0x8, 0x4); // lbu $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_23_00_00); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_23_00_00); state.registers[8] = 0; // t0 state.registers[9] = t1; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -516,12 +526,12 @@ contract MIPS_Test is CommonTest { function test_lhu_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x25, 0x9, 0x8, 0x4); // lhu $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_23_00_00); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_23_00_00); state.registers[8] = 0; // t0 state.registers[9] = t1; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -536,11 +546,11 @@ contract MIPS_Test is CommonTest { function test_lwl_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x22, 0x9, 0x8, 0x4); // lwl $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -563,11 +573,11 @@ contract MIPS_Test is CommonTest { function test_lwr_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x26, 0x9, 0x8, 0x4); // lwr $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -591,11 +601,11 @@ contract MIPS_Test is CommonTest { uint32 t1 = 0x100; uint32 insn = encodeitype(0x28, 0x9, 0x8, 0x4); // sb $t0, 4($t1) // note. cannon memory is zero-initalized. mem[t+4] = 0 is a no-op - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xdd_00_00_00); expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -610,11 +620,11 @@ contract MIPS_Test is CommonTest { function test_sh_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x29, 0x9, 0x8, 0x4); // sh $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xcc_dd_00_00); expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -629,11 +639,11 @@ contract MIPS_Test is CommonTest { function test_swl_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x2a, 0x9, 0x8, 0x4); // swl $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd); expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -648,11 +658,11 @@ contract MIPS_Test is CommonTest { function test_sw_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x2b, 0x9, 0x8, 0x4); // sw $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd); expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -667,11 +677,11 @@ contract MIPS_Test is CommonTest { function test_swr_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x2e, 0x9, 0x8, 0x5); // swr $t0, 5($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xcc_dd_00_00); expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -688,12 +698,12 @@ contract MIPS_Test is CommonTest { uint32 val = 0x12_23_45_67; uint32 insn = encodeitype(0x30, 0x9, 0x8, 0x4); // ll $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, val); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, val); state.registers[8] = 0; // t0 state.registers[9] = t1; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -708,11 +718,11 @@ contract MIPS_Test is CommonTest { function test_sc_succeeds() external { uint32 t1 = 0x100; uint32 insn = encodeitype(0x38, 0x9, 0x8, 0x4); // sc $t0, 4($t1) - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, t1 + 4, 0); state.registers[8] = 0xaa_bb_cc_dd; // t0 state.registers[9] = t1; - MIPS.State memory expect; + IMIPS.State memory expect; (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd); expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -727,12 +737,12 @@ contract MIPS_Test is CommonTest { function test_movn_succeeds() external { // test mips mov instruction uint32 insn = encodespec(0x9, 0xa, 0x8, 0xb); // movn $t0, $t1, $t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xa; // t0 state.registers[9] = 0xb; // t1 state.registers[10] = 0x1; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -754,12 +764,12 @@ contract MIPS_Test is CommonTest { function test_movz_succeeds() external { // test mips mov instruction uint32 insn = encodespec(0x9, 0xa, 0x8, 0xa); // movz $t0, $t1, $t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xa; // t0 state.registers[9] = 0xb; // t1 state.registers[10] = 0x0; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -780,10 +790,10 @@ contract MIPS_Test is CommonTest { function test_mflo_succeeds() external { uint32 insn = encodespec(0x0, 0x0, 0x8, 0x12); // mflo $t0 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.lo = 0xdeadbeef; - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -797,10 +807,10 @@ contract MIPS_Test is CommonTest { function test_mfhi_succeeds() external { uint32 insn = encodespec(0x0, 0x0, 0x8, 0x10); // mfhi $t0 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.hi = 0xdeadbeef; - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -814,10 +824,10 @@ contract MIPS_Test is CommonTest { function test_mthi_succeeds() external { uint32 insn = encodespec(0x8, 0x0, 0x0, 0x11); // mthi $t0 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xdeadbeef; // t0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -831,10 +841,10 @@ contract MIPS_Test is CommonTest { function test_mtlo_succeeds() external { uint32 insn = encodespec(0x8, 0x0, 0x0, 0x13); // mtlo $t0 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xdeadbeef; // t0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -848,11 +858,11 @@ contract MIPS_Test is CommonTest { function test_mul_succeeds() external { uint32 insn = encodespec2(0x9, 0xa, 0x8, 0x2); // mul t0, t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 5; // t1 state.registers[10] = 2; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -867,11 +877,11 @@ contract MIPS_Test is CommonTest { function test_mult_succeeds() external { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x18); // mult t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x0F_FF_00_00; // t1 state.registers[10] = 100; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -888,11 +898,11 @@ contract MIPS_Test is CommonTest { function test_multu_succeeds() external { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x19); // multu t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x0F_FF_00_00; // t1 state.registers[10] = 100; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -909,11 +919,11 @@ contract MIPS_Test is CommonTest { function test_div_succeeds() external { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1a); // div t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 5; // t1 state.registers[10] = 2; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -930,11 +940,11 @@ contract MIPS_Test is CommonTest { function test_divu_succeeds() external { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1b); // divu t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 5; // t1 state.registers[10] = 2; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -951,7 +961,7 @@ contract MIPS_Test is CommonTest { function test_div_byZero_fails() external { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1a); // div t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 5; // t1 state.registers[10] = 0; // t2 @@ -961,7 +971,7 @@ contract MIPS_Test is CommonTest { function test_divu_byZero_fails() external { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1b); // divu t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 5; // t1 state.registers[10] = 0; // t2 @@ -972,11 +982,11 @@ contract MIPS_Test is CommonTest { function test_beq_succeeds() external { uint16 boff = 0x10; uint32 insn = encodeitype(0x4, 0x9, 0x8, boff); // beq $t0, $t1, 16 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xdeadbeef; // t0 state.registers[9] = 0xdeadbeef; // t1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + (uint32(boff) << 2); @@ -998,11 +1008,11 @@ contract MIPS_Test is CommonTest { function test_bne_succeeds() external { uint16 boff = 0x10; uint32 insn = encodeitype(0x5, 0x9, 0x8, boff); // bne $t0, $t1, 16 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xdeadbeef; // t0 state.registers[9] = 0xaa; // t1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + (uint32(boff) << 2); @@ -1017,10 +1027,10 @@ contract MIPS_Test is CommonTest { function test_blez_succeeds() external { uint16 boff = 0x10; uint32 insn = encodeitype(0x6, 0x8, 0x0, boff); // blez $t0, 16 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0; // t0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + (uint32(boff) << 2); @@ -1034,10 +1044,10 @@ contract MIPS_Test is CommonTest { function test_bgtz_succeeds() external { uint16 boff = 0xa0; uint32 insn = encodeitype(0x7, 0x8, 0x0, boff); // bgtz $t0, 0xa0 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 1; // t0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + (uint32(boff) << 2); @@ -1052,10 +1062,10 @@ contract MIPS_Test is CommonTest { function test_bltz_succeeds() external { uint16 boff = 0x10; uint32 insn = encodeitype(0x1, 0x8, 0x0, boff); // bltz $t0, 16 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xF0_00_00_00; // t0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + (uint32(boff) << 2); @@ -1069,10 +1079,10 @@ contract MIPS_Test is CommonTest { function test_bgez_succeeds() external { uint16 boff = 0x10; uint32 insn = encodeitype(0x1, 0x8, 0x1, boff); // bgez $t0, 16 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0x00_00_00_01; // t0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + (uint32(boff) << 2); @@ -1086,9 +1096,9 @@ contract MIPS_Test is CommonTest { function test_jump_succeeds() external { uint32 label = 0x02_00_00_02; // set the 26th bit to assert no sign extension uint32 insn = uint32(0x08_00_00_00) | label; // j label - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = label << 2; @@ -1102,9 +1112,9 @@ contract MIPS_Test is CommonTest { uint32 pcRegion1 = 0x10000000; uint32 label = 0x2; uint32 insn = uint32(0x08_00_00_00) | label; // j label - (MIPS.State memory state, bytes memory proof) = constructMIPSState(pcRegion1, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(pcRegion1, insn, 0x4, 0); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = (state.nextPC & 0xF0_00_00_00) | (uint32(label) << 2); @@ -1118,9 +1128,9 @@ contract MIPS_Test is CommonTest { function test_jal_succeeds() external { uint32 label = 0x02_00_00_02; // set the 26th bit to assert no sign extension uint32 insn = uint32(0x0c_00_00_00) | label; // jal label - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = label << 2; @@ -1135,9 +1145,9 @@ contract MIPS_Test is CommonTest { uint32 pcRegion1 = 0x10000000; uint32 label = 0x2; uint32 insn = uint32(0x0c_00_00_00) | label; // jal label - (MIPS.State memory state, bytes memory proof) = constructMIPSState(pcRegion1, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(pcRegion1, insn, 0x4, 0); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = (state.nextPC & 0xF0_00_00_00) | (uint32(label) << 2); @@ -1151,10 +1161,10 @@ contract MIPS_Test is CommonTest { function test_jr_succeeds() external { uint16 tgt = 0x34; uint32 insn = encodespec(0x8, 0, 0, 0x8); // jr t0 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = tgt; - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = tgt; @@ -1168,10 +1178,10 @@ contract MIPS_Test is CommonTest { function test_jalr_succeeds() external { uint16 tgt = 0x34; uint32 insn = encodespec(0x8, 0, 0x9, 0x9); // jalr t1, t0 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = tgt; // t0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = tgt; @@ -1186,10 +1196,10 @@ contract MIPS_Test is CommonTest { function test_sll_succeeds() external { uint8 shiftamt = 4; uint32 insn = encodespec(0x0, 0x9, 0x8, uint16(shiftamt) << 6); // sll t0, t1, 3 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x20; // t1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1205,10 +1215,10 @@ contract MIPS_Test is CommonTest { function test_srl_succeeds() external { uint8 shiftamt = 4; uint32 insn = encodespec(0x0, 0x9, 0x8, uint16(shiftamt) << 6 | 2); // srl t0, t1, 3 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x20; // t1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1224,10 +1234,10 @@ contract MIPS_Test is CommonTest { function test_sra_succeeds() external { uint8 shiftamt = 4; uint32 insn = encodespec(0x0, 0x9, 0x8, uint16(shiftamt) << 6 | 3); // sra t0, t1, 3 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x80_00_00_20; // t1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1242,11 +1252,11 @@ contract MIPS_Test is CommonTest { function test_sllv_succeeds() external { uint32 insn = encodespec(0xa, 0x9, 0x8, 4); // sllv t0, t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x20; // t1 state.registers[10] = 4; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1262,11 +1272,11 @@ contract MIPS_Test is CommonTest { function test_srlv_succeeds() external { uint32 insn = encodespec(0xa, 0x9, 0x8, 6); // srlv t0, t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x20_00; // t1 state.registers[10] = 4; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1282,11 +1292,11 @@ contract MIPS_Test is CommonTest { function test_srav_succeeds() external { uint32 insn = encodespec(0xa, 0x9, 0x8, 7); // srav t0, t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0xdeafbeef; // t1 state.registers[10] = 12; // t2 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1310,14 +1320,14 @@ contract MIPS_Test is CommonTest { _rs = uint32(bound(uint256(_rs), 0x20, type(uint32).max)); uint32 insn = encodespec(0xa, 0x9, 0x8, 7); // srav t0, t1, t2 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0xdeadbeef; // t1 state.registers[10] = _rs; // t2 // Calculate shamt uint32 shamt = state.registers[10] & 0x1F; - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1333,10 +1343,10 @@ contract MIPS_Test is CommonTest { function test_lui_succeeds() external { uint32 insn = encodeitype(0xf, 0x0, 0x8, 0x4); // lui $t0, 0x04 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1349,10 +1359,10 @@ contract MIPS_Test is CommonTest { function test_clo_succeeds() external { uint32 insn = encodespec2(0x9, 0x0, 0x8, 0x21); // clo t0, t1 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0xFF_00_00_00; // t1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1366,10 +1376,10 @@ contract MIPS_Test is CommonTest { function test_clz_succeeds() external { uint32 insn = encodespec2(0x9, 0x0, 0x8, 0x20); // clz t0, t1 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[9] = 0x00_00_F0_00; // t1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1394,7 +1404,7 @@ contract MIPS_Test is CommonTest { registers[5] = a1; // addr registers[6] = 4; // count - MIPS.State memory state = MIPS.State({ + IMIPS.State memory state = IMIPS.State({ memRoot: memRoot, preimageKey: bytes32(uint256(1) << 248 | 0x01), preimageOffset: 8, // start reading past the pre-image length prefix @@ -1416,7 +1426,7 @@ contract MIPS_Test is CommonTest { uint8 partOffset = 8; oracle.loadLocalData(uint256(state.preimageKey), 0, word, size, partOffset); - MIPS.State memory expect = state; + IMIPS.State memory expect = state; expect.preimageOffset += 4; expect.pc = state.nextPC; expect.nextPC += 4; @@ -1443,7 +1453,7 @@ contract MIPS_Test is CommonTest { registers[5] = a1; // addr registers[6] = 4; // count - MIPS.State memory state = MIPS.State({ + IMIPS.State memory state = IMIPS.State({ memRoot: memRoot, preimageKey: bytes32(0), preimageOffset: 1, @@ -1459,7 +1469,7 @@ contract MIPS_Test is CommonTest { }); bytes memory encodedState = encodeState(state); - MIPS.State memory expect = state; + IMIPS.State memory expect = state; expect.preimageOffset = 0; // preimage write resets offset expect.pc = state.nextPC; expect.nextPC += 4; @@ -1476,7 +1486,7 @@ contract MIPS_Test is CommonTest { uint32 insn = 0x0000000c; // syscall (bytes32 memRoot, bytes memory proof) = ffi.getCannonMemoryProof(0, insn); - MIPS.State memory state; + IMIPS.State memory state; state.memRoot = memRoot; state.nextPC = 4; state.registers[2] = 4090; // mmap syscall @@ -1484,7 +1494,7 @@ contract MIPS_Test is CommonTest { state.registers[5] = 4095; // a1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; // assert page allocation is aligned to 4k expect.step = state.step + 1; @@ -1499,11 +1509,11 @@ contract MIPS_Test is CommonTest { assertEq(postState, outputState(expect), "unexpected post state"); } - function test_mmap_succeeds_justWithinMemLimit() external { + function test_mmap_justWithinMemLimit_succeeds() external { uint32 insn = 0x0000000c; // syscall (bytes32 memRoot, bytes memory proof) = ffi.getCannonMemoryProof(0, insn); - MIPS.State memory state; + IMIPS.State memory state; state.memRoot = memRoot; state.nextPC = 4; state.heap = sys.HEAP_END - 4096; // Set up to increase heap to its limit @@ -1512,7 +1522,7 @@ contract MIPS_Test is CommonTest { state.registers[5] = 4095; // a1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; // assert page allocation is aligned to 4k expect.step = state.step + 1; @@ -1528,11 +1538,11 @@ contract MIPS_Test is CommonTest { assertEq(postState, outputState(expect), "unexpected post state"); } - function test_mmap_fails() external { + function test_step_mmap_fails() external { uint32 insn = 0x0000000c; // syscall (bytes32 memRoot, bytes memory proof) = ffi.getCannonMemoryProof(0, insn); - MIPS.State memory state; + IMIPS.State memory state; state.memRoot = memRoot; state.nextPC = 4; state.heap = sys.HEAP_END - 4096; // Set up to increase heap beyond its limit @@ -1541,7 +1551,7 @@ contract MIPS_Test is CommonTest { state.registers[5] = 4097; // a1 bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; // assert page allocation is aligned to 4k expect.step = state.step + 1; @@ -1559,12 +1569,12 @@ contract MIPS_Test is CommonTest { function test_brk_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[2] = 4045; // brk syscall state.registers[4] = 0xdead; bytes memory encodedState = encodeState(state); - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.step = state.step + 1; expect.pc = state.nextPC; @@ -1578,10 +1588,10 @@ contract MIPS_Test is CommonTest { function test_clone_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[2] = 4120; // clone syscall - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.step = state.step + 1; expect.pc = state.nextPC; @@ -1595,11 +1605,11 @@ contract MIPS_Test is CommonTest { function test_exit_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[2] = 4246; // exit_group syscall state.registers[4] = 0x5; // a0 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.pc; expect.nextPC = state.nextPC; @@ -1615,12 +1625,12 @@ contract MIPS_Test is CommonTest { function test_fcntl_getfl_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[2] = 4055; // fcntl syscall state.registers[4] = 0x0; // a0 state.registers[5] = 0x3; // a1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1641,12 +1651,12 @@ contract MIPS_Test is CommonTest { function test_fcntl_getfd_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[2] = 4055; // fcntl syscall state.registers[4] = 0x0; // a0 state.registers[5] = 0x1; // a1 - MIPS.State memory expect; + IMIPS.State memory expect; expect.memRoot = state.memRoot; expect.pc = state.nextPC; expect.nextPC = state.nextPC + 4; @@ -1660,7 +1670,7 @@ contract MIPS_Test is CommonTest { function test_prestate_exited_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.exited = true; bytes memory enc = encodeState(state); @@ -1675,16 +1685,16 @@ contract MIPS_Test is CommonTest { uint32 addr = 0xFF_FF_FF_FC; // 4-byte aligned ff..ff (bytes32 memRoot, bytes memory proof) = ffi.getCannonMemoryProof(0, illegal_insn, addr, 0); - MIPS.State memory state; + IMIPS.State memory state; state.memRoot = memRoot; bytes memory encodedState = encodeState(state); vm.expectRevert("invalid instruction"); mips.step(encodedState, proof, 0); } - function test_invalid_root_fails() external { + function test_step_invalidRoot_fails() external { uint32 insn = 0x0000000c; // syscall - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[2] = 4246; // exit_group syscall state.registers[4] = 0x5; // a0 @@ -1696,14 +1706,14 @@ contract MIPS_Test is CommonTest { mips.step(encodeState(state), proof, 0); } - function test_invalid_root_different_leaf_fails() external { + function test_step_invalidRootDifferentLeaf_fails() external { uint32 insn = 0x0000000c; // syscall // Initialize the state, though for the proof, use valid proofs for the instruction // and the memory address, but for a different leaf that does not contain the // instruction @ pc nor the memory address being read. uint32 pc = 0; - MIPS.State memory state; + IMIPS.State memory state; bytes memory proof; (state.memRoot, proof) = ffi.getCannonMemoryProofWrongLeaf(pc, insn, 0x4, 0); state.pc = pc; @@ -1718,7 +1728,7 @@ contract MIPS_Test is CommonTest { function test_jump_inDelaySlot_fails() external { uint16 label = 0x2; uint32 insn = uint32(0x08_00_00_00) | label; // j label - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.nextPC = 0xa; vm.expectRevert("jump in delay slot"); @@ -1728,7 +1738,7 @@ contract MIPS_Test is CommonTest { function test_branch_inDelaySlot_fails() external { uint16 boff = 0x10; uint32 insn = encodeitype(0x4, 0x9, 0x8, boff); // beq $t0, $t1, 16 - (MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); + (IMIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0); state.registers[8] = 0xdeadbeef; // t0 state.registers[9] = 0xdeadbeef; // t1 state.nextPC = 0xa; @@ -1737,7 +1747,7 @@ contract MIPS_Test is CommonTest { mips.step(encodeState(state), proof, 0); } - function encodeState(MIPS.State memory state) internal pure returns (bytes memory) { + function encodeState(IMIPS.State memory state) internal pure returns (bytes memory) { bytes memory registers; for (uint256 i = 0; i < state.registers.length; i++) { registers = bytes.concat(registers, abi.encodePacked(state.registers[i])); @@ -1763,7 +1773,7 @@ contract MIPS_Test is CommonTest { /// 1. Exited with success (Invalid) /// 2. Exited with failure (Panic) /// 3. Unfinished - function vmStatus(MIPS.State memory state) internal pure returns (VMStatus out_) { + function vmStatus(IMIPS.State memory state) internal pure returns (VMStatus out_) { if (!state.exited) { return VMStatuses.UNFINISHED; } else if (state.exitCode == 0) { @@ -1775,7 +1785,7 @@ contract MIPS_Test is CommonTest { } } - function outputState(MIPS.State memory state) internal pure returns (bytes32 out_) { + function outputState(IMIPS.State memory state) internal pure returns (bytes32 out_) { bytes memory enc = encodeState(state); VMStatus status = vmStatus(state); assembly { @@ -1791,22 +1801,22 @@ contract MIPS_Test is CommonTest { uint32 val ) internal - returns (MIPS.State memory state, bytes memory proof) + returns (IMIPS.State memory state_, bytes memory proof_) { - (state.memRoot, proof) = ffi.getCannonMemoryProof(pc, insn, addr, val); - state.pc = pc; - state.nextPC = pc + 4; + (state_.memRoot, proof_) = ffi.getCannonMemoryProof(pc, insn, addr, val); + state_.pc = pc; + state_.nextPC = pc + 4; } - function encodeitype(uint8 opcode, uint8 rs, uint8 rt, uint16 imm) internal pure returns (uint32 insn) { - insn = uint32(opcode) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | imm; + function encodeitype(uint8 opcode, uint8 rs, uint8 rt, uint16 imm) internal pure returns (uint32 insn_) { + insn_ = uint32(opcode) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | imm; } - function encodespec(uint8 rs, uint8 rt, uint8 rd, uint16 funct) internal pure returns (uint32 insn) { - insn = uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); + function encodespec(uint8 rs, uint8 rt, uint8 rd, uint16 funct) internal pure returns (uint32 insn_) { + insn_ = uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); } - function encodespec2(uint8 rs, uint8 rt, uint8 rd, uint8 funct) internal pure returns (uint32 insn) { - insn = uint32(28) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); + function encodespec2(uint8 rs, uint8 rt, uint8 rd, uint8 funct) internal pure returns (uint32 insn_) { + insn_ = uint32(28) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); } } diff --git a/packages/contracts-bedrock/test/cannon/MIPS2.t.sol b/packages/contracts-bedrock/test/cannon/MIPS2.t.sol index 59b3e9e17eb4c..07c5883c17bca 100644 --- a/packages/contracts-bedrock/test/cannon/MIPS2.t.sol +++ b/packages/contracts-bedrock/test/cannon/MIPS2.t.sol @@ -4,9 +4,8 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; -// Contracts -import { MIPS2 } from "src/cannon/MIPS2.sol"; -import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; +// Scripts +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Libraries import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol"; @@ -15,13 +14,14 @@ import { InvalidExitedValue, InvalidMemoryProof, InvalidSecondMemoryProof } from import "src/dispute/lib/Types.sol"; // Interfaces -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { IMIPS2 } from "interfaces/cannon/IMIPS2.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; contract ThreadStack { bytes32 internal constant EMPTY_THREAD_ROOT = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; struct Entry { - MIPS2.ThreadState thread; + IMIPS2.ThreadState thread; bytes32 root; } @@ -40,15 +40,15 @@ contract ThreadStack { root_ = stack[stack.length - 1 - _i].root; } - function top() public view returns (MIPS2.ThreadState memory thread_) { + function top() public view returns (IMIPS2.ThreadState memory thread_) { thread_ = peek(0); } - function peek(uint256 _i) public view returns (MIPS2.ThreadState memory thread_) { + function peek(uint256 _i) public view returns (IMIPS2.ThreadState memory thread_) { thread_ = stack[stack.length - 1 - _i].thread; } - function push(MIPS2.ThreadState memory _thread) public { + function push(IMIPS2.ThreadState memory _thread) public { _push(_thread); } @@ -56,12 +56,12 @@ contract ThreadStack { stack.pop(); } - function replace(MIPS2.ThreadState memory _thread) public { + function replace(IMIPS2.ThreadState memory _thread) public { stack.pop(); _push(_thread); } - function _push(MIPS2.ThreadState memory _thread) internal { + function _push(IMIPS2.ThreadState memory _thread) internal { bytes32 newRoot = keccak256(abi.encodePacked(stack[stack.length - 1].root, keccak256(encodeThread(_thread)))); stack.push(Entry(_thread, newRoot)); } @@ -79,7 +79,7 @@ contract Threading { traverseRight = false; } - function createThread() public returns (MIPS2.ThreadState memory thread_) { + function createThread() public returns (IMIPS2.ThreadState memory thread_) { thread_.threadID = nextThreadID; if (traverseRight) { right.push(thread_); @@ -89,7 +89,7 @@ contract Threading { nextThreadID += 1; } - function current() public view returns (MIPS2.ThreadState memory out_) { + function current() public view returns (IMIPS2.ThreadState memory out_) { if (traverseRight) { out_ = right.top(); } else { @@ -97,7 +97,7 @@ contract Threading { } } - function replaceCurrent(MIPS2.ThreadState memory _thread) public { + function replaceCurrent(IMIPS2.ThreadState memory _thread) public { if (traverseRight) { right.replace(_thread); } else { @@ -119,8 +119,8 @@ contract Threading { } contract MIPS2_Test is CommonTest { - MIPS2 internal mips; - PreimageOracle internal oracle; + IMIPS2 internal mips; + IPreimageOracle internal oracle; Threading internal threading; // keccak256(bytes32(0) ++ bytes32(0)) @@ -134,8 +134,20 @@ contract MIPS2_Test is CommonTest { function setUp() public virtual override { super.setUp(); - oracle = new PreimageOracle(0, 0); - mips = new MIPS2(IPreimageOracle(address(oracle))); + oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ); + mips = IMIPS2( + DeployUtils.create1({ + _name: "MIPS2", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IMIPS2.__constructor__, (IPreimageOracle(address(oracle)))) + ) + }) + ); threading = new Threading(); vm.store(address(mips), 0x0, bytes32(abi.encode(address(oracle)))); vm.label(address(oracle), "PreimageOracle"); @@ -147,7 +159,7 @@ contract MIPS2_Test is CommonTest { /// This is useful to more easily debug non-forge tests. /// For example, in cannon/mipsevm/evm_test.go step input can be pulled here: /// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80 - function test_mips2_step_debug_succeeds() external { + function test_step_debug_succeeds() external { bytes memory input = hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acab5a39c6f974b22302e96dcdef1815483eaf580639bb1ee7ac98267afac2bf1ac041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81fa7c3d90b000000000000000000000078fc2ffac2fd940100000000000080c8ffffffff006504aeffb6e08baf3f85da5476a9160fa8f9f188a722fdd29268b0cbaf596736ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000f1f85ff4f1f85ff8506d442dbb3938f83eb60825a7ecbff2000010185e1a31f600050f0000000064a7c3d90be5acea102ad7bda149e0bfd0e7111c77d98b335645e665389becadf140ef999cc64fbd7f04799e85c97dadc5cca510bd5b3d97166d1aec28829f3dd43d8cf1f9358e4103b16d09d466e2c7c048ea3ba1aef3141e700270581aa0b75b50e34fc926bb2d83ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; (bool success, bytes memory retVal) = address(mips).call(input); @@ -163,7 +175,7 @@ contract MIPS2_Test is CommonTest { registers[0] = 0xdeadbeef; registers[16] = 0xbfff0000; registers[31] = 0x0badf00d; - MIPS2.ThreadState memory thread = MIPS2.ThreadState({ + IMIPS2.ThreadState memory thread = IMIPS2.ThreadState({ threadID: 0, exitCode: 0, exited: false, @@ -180,12 +192,12 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodedThread, EMPTY_THREAD_ROOT); bytes32 threadRoot = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodedThread))); - MIPS2.State memory state = MIPS2.State({ + IMIPS2.State memory state = IMIPS2.State({ memRoot: hex"30be14bdf94d7a93989a6263f1e116943dc052d584730cae844bf330dfddce2f", preimageKey: bytes32(0), preimageOffset: 0, heap: 0, - llReservationActive: false, + llReservationStatus: 0, llAddress: 0, llOwnerThread: 0, exitCode: 0, @@ -211,7 +223,7 @@ contract MIPS2_Test is CommonTest { for (uint8 exited = 2; exited <= type(uint8).max && exited != 0;) { // Setup state uint32 insn = encodespec(17, 18, 8, 0x20); // Arbitrary instruction: add t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); // Set up step data @@ -234,9 +246,9 @@ contract MIPS2_Test is CommonTest { } } - function test_invalidThreadWitness_reverts() public { - MIPS2.State memory state; - MIPS2.ThreadState memory thread; + function test_step_invalidThreadWitness_reverts() public { + IMIPS2.State memory state; + IMIPS2.ThreadState memory thread; bytes memory memProof; bytes memory witness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); vm.expectRevert("invalid thread witness"); @@ -245,19 +257,19 @@ contract MIPS2_Test is CommonTest { function test_syscallNanosleep_succeeds() public { uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[2] = sys.SYS_NANOSLEEP; thread.registers[7] = 0xdead; // should be reset to a zero errno bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 0x0; expectThread.registers[7] = 0x0; - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; expect.leftThreadStack = EMPTY_THREAD_ROOT; @@ -270,19 +282,19 @@ contract MIPS2_Test is CommonTest { function test_syscallSchedYield_succeeds() public { uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[2] = sys.SYS_SCHED_YIELD; thread.registers[7] = 0xdead; // should be reset to a zero errno bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 0x0; expectThread.registers[7] = 0x0; - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; expect.leftThreadStack = EMPTY_THREAD_ROOT; @@ -295,7 +307,7 @@ contract MIPS2_Test is CommonTest { function test_syscallGetTID_succeeds() public { uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.threadID = 0xbeef; thread.registers[2] = sys.SYS_GETTID; @@ -303,12 +315,12 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 0xbeef; expectThread.registers[7] = 0x0; // errno - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; expect.leftThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(expectThread)))); @@ -321,7 +333,7 @@ contract MIPS2_Test is CommonTest { uint32 insn = 0x0000000c; // syscall uint32 sp = 0xdead; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[2] = sys.SYS_CLONE; thread.registers[A0_REG] = sys.VALID_SYS_CLONE_FLAGS; @@ -329,13 +341,13 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = state.nextThreadID; expectThread.registers[7] = 0; - MIPS2.ThreadState memory newThread = copyThread(thread); + IMIPS2.ThreadState memory newThread = copyThread(thread); newThread.threadID = 1; newThread.futexAddr = sys.FUTEX_EMPTY_ADDR; newThread.futexVal = 0; @@ -346,7 +358,7 @@ contract MIPS2_Test is CommonTest { newThread.registers[7] = 0; newThread.registers[SP_REG] = sp; - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.nextThreadID = 2; expect.stepsSinceLastContextSwitch = 0; @@ -362,7 +374,7 @@ contract MIPS2_Test is CommonTest { uint32 insn = 0x0000000c; // syscall uint32 sp = 0xdead; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[2] = sys.SYS_CLONE; thread.registers[A0_REG] = 0xdead; // invalid flag @@ -370,13 +382,13 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = state.nextThreadID; expectThread.registers[7] = 0; - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.step + 1; expect.exited = true; @@ -393,7 +405,7 @@ contract MIPS2_Test is CommonTest { uint32 timeout = 1; uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, futexAddr, futexVal); thread.registers[2] = sys.SYS_FUTEX; thread.registers[A0_REG] = futexAddr; @@ -406,13 +418,13 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // FUTEX_WAIT - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.futexAddr = futexAddr; expectThread.futexVal = futexVal; expectThread.futexTimeoutStep = state.step + 1 + sys.FUTEX_TIMEOUT_STEPS; threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; finalizeThreadingState(threading, expect); @@ -428,7 +440,7 @@ contract MIPS2_Test is CommonTest { uint32 timeout = 0; uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, futexAddr, futexVal); thread.registers[2] = sys.SYS_FUTEX; thread.registers[A0_REG] = futexAddr; @@ -441,13 +453,13 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // FUTEX_WAIT - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.futexAddr = futexAddr; expectThread.futexVal = futexVal; expectThread.futexTimeoutStep = sys.FUTEX_NO_TIMEOUT; threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; finalizeThreadingState(threading, expect); @@ -462,7 +474,7 @@ contract MIPS2_Test is CommonTest { uint32 futexVal = 0xAA_AA_AA_AA; uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, futexAddr, futexVal); thread.registers[2] = sys.SYS_FUTEX; thread.registers[A0_REG] = futexAddr; @@ -475,7 +487,7 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // FUTEX_WAIT - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR; @@ -483,7 +495,7 @@ contract MIPS2_Test is CommonTest { expectThread.registers[7] = sys.EAGAIN; // errno threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; finalizeThreadingState(threading, expect); @@ -495,7 +507,7 @@ contract MIPS2_Test is CommonTest { function test_syscallFutexWake_succeeds() public { uint32 futexAddr = 0x1000; uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, futexAddr, 0xAA_AA_AA_AA); thread.threadID = threading.nextThreadID(); thread.registers[2] = sys.SYS_FUTEX; @@ -510,14 +522,14 @@ contract MIPS2_Test is CommonTest { // FUTEX_WAKE threading.left().pop(); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 0x0; expectThread.registers[7] = 0x0; // errno threading.right().push(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.wakeup = futexAddr; expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; @@ -533,13 +545,13 @@ contract MIPS2_Test is CommonTest { uint32 insn = 0x0000000c; // syscall uint8 exitCode = 4; - MIPS2.ThreadState memory threadA = threading.createThread(); + IMIPS2.ThreadState memory threadA = threading.createThread(); threadA.futexAddr = sys.FUTEX_EMPTY_ADDR; threadA.pc = 0x1000; threadA.nextPC = 0x1004; threading.replaceCurrent(threadA); - MIPS2.ThreadState memory threadB = threading.createThread(); + IMIPS2.ThreadState memory threadB = threading.createThread(); threadB.futexAddr = sys.FUTEX_EMPTY_ADDR; threadB.pc = 0x100; threadB.nextPC = 0x104; @@ -548,7 +560,7 @@ contract MIPS2_Test is CommonTest { threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; bytes memory memProof; (state.memRoot, memProof) = ffi.getCannonMemoryProof(threadB.pc, insn, 0, 0); state.step = 20; @@ -557,11 +569,11 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // state updates - MIPS2.ThreadState memory expectThread = copyThread(threadB); + IMIPS2.ThreadState memory expectThread = copyThread(threadB); expectThread.exited = true; expectThread.exitCode = exitCode; threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; finalizeThreadingState(threading, expect); @@ -575,7 +587,7 @@ contract MIPS2_Test is CommonTest { uint32 insn = 0x0000000c; // syscall uint8 exitCode = 4; - MIPS2.ThreadState memory thread = threading.createThread(); + IMIPS2.ThreadState memory thread = threading.createThread(); thread.futexAddr = sys.FUTEX_EMPTY_ADDR; thread.pc = 0x1000; thread.nextPC = 0x1004; @@ -584,7 +596,7 @@ contract MIPS2_Test is CommonTest { threading.replaceCurrent(thread); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; bytes memory memProof; (state.memRoot, memProof) = ffi.getCannonMemoryProof(thread.pc, insn, 0, 0); state.step = 20; @@ -593,11 +605,11 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // state updates - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.exited = true; expectThread.exitCode = exitCode; threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; expect.exited = true; @@ -610,19 +622,19 @@ contract MIPS2_Test is CommonTest { function test_syscallGetPid_succeeds() public { uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[2] = sys.SYS_GETPID; thread.registers[7] = 0xdead; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 0x0; expectThread.registers[7] = 0x0; - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; expect.leftThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(expectThread)))); @@ -645,7 +657,7 @@ contract MIPS2_Test is CommonTest { uint32 pc = 0; uint32 insn = 0x0000000c; // syscall uint32 timespecAddr = 0xb000; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = constructMIPSState(pc, insn, timespecAddr, 0xbad); state.step = 100_000_004; thread.registers[2] = sys.SYS_CLOCKGETTIME; @@ -663,11 +675,11 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); (, bytes memory memProof2) = ffi.getCannonMemoryProof2(pc, insn, timespecAddr, secs, timespecAddr + 4); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); (expect.memRoot,) = ffi.getCannonMemoryProof(pc, insn, timespecAddr, secs, timespecAddr + 4, nsecs); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 0x0; @@ -695,7 +707,7 @@ contract MIPS2_Test is CommonTest { uint32 insn = 0x0000000c; // syscall uint32 timespecAddr = 0xb001; uint32 timespecAddrAligned = 0xb000; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = constructMIPSState(pc, insn, timespecAddrAligned, 0xbad); state.step = 100_000_004; thread.registers[2] = sys.SYS_CLOCKGETTIME; @@ -714,12 +726,12 @@ contract MIPS2_Test is CommonTest { (, bytes memory memProof2) = ffi.getCannonMemoryProof2(pc, insn, timespecAddrAligned, secs, timespecAddrAligned + 4); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); (expect.memRoot,) = ffi.getCannonMemoryProof(pc, insn, timespecAddrAligned, secs, timespecAddrAligned + 4, nsecs); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 0x0; @@ -731,12 +743,12 @@ contract MIPS2_Test is CommonTest { } /// @dev Test asserting that an clock_gettime monotonic syscall reverts on an invalid memory proof - function test_syscallClockGettimeMonotonicInvalidProof_reverts() public { + function test_step_syscallClockGettimeMonotonicInvalidProof_reverts() public { _test_syscallClockGettimeInvalidProof_reverts(sys.CLOCK_GETTIME_MONOTONIC_FLAG); } /// @dev Test asserting that an clock_gettime realtime syscall reverts on an invalid memory proof - function test_syscallClockGettimeRealtimeInvalidProof_reverts() public { + function test_step_syscallClockGettimeRealtimeInvalidProof_reverts() public { _test_syscallClockGettimeInvalidProof_reverts(sys.CLOCK_GETTIME_REALTIME_FLAG); } @@ -754,7 +766,7 @@ contract MIPS2_Test is CommonTest { uint32 pc = 0; uint32 insn = 0x0000000c; // syscall uint32 timespecAddr = 0xb000; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = constructMIPSState(pc, insn, timespecAddr, 0xbad); state.step = 100_000_004; thread.registers[2] = sys.SYS_CLOCKGETTIME; @@ -777,8 +789,9 @@ contract MIPS2_Test is CommonTest { vm.expectRevert(InvalidMemoryProof.selector); mips.step(encodeState(state), bytes.concat(threadWitness, invalidInsnAndMemProof, memProof2), 0); - (, bytes memory invalidMemProof2) = - ffi.getCannonMemoryProof2(pc, insn, timespecAddr, secs + 1, timespecAddr + 4); + uint32 _secs = secs + 1; + uint32 _timespecAddr = timespecAddr + 4; + (, bytes memory invalidMemProof2) = ffi.getCannonMemoryProof2(pc, insn, timespecAddr, _secs, _timespecAddr); vm.expectRevert(InvalidSecondMemoryProof.selector); mips.step(encodeState(state), bytes.concat(threadWitness, insnAndMemProof, invalidMemProof2), 0); } @@ -789,7 +802,7 @@ contract MIPS2_Test is CommonTest { uint32 pc = 0; uint32 insn = 0x0000000c; // syscall uint32 timespecAddr = 0xb000; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory insnAndMemProof) = constructMIPSState(pc, insn, timespecAddr, 0xbad); state.step = (sys.HZ * 10 + 5) - 1; thread.registers[2] = sys.SYS_CLOCKGETTIME; @@ -799,12 +812,12 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = sys.SYS_ERROR_SIGNAL; expectThread.registers[7] = sys.EINVAL; - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; expect.leftThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(expectThread)))); @@ -815,14 +828,14 @@ contract MIPS2_Test is CommonTest { /// @dev Static unit test asserting that VM preempts threads after a certain number of steps function test_threadQuantumSchedule_succeeds() public { - MIPS2.ThreadState memory threadA = threading.createThread(); + IMIPS2.ThreadState memory threadA = threading.createThread(); threadA.threadID = 0; threadA.futexAddr = sys.FUTEX_EMPTY_ADDR; threading.replaceCurrent(threadA); - MIPS2.ThreadState memory threadB = threading.createThread(); + IMIPS2.ThreadState memory threadB = threading.createThread(); threadB.futexAddr = sys.FUTEX_EMPTY_ADDR; threading.replaceCurrent(threadB); - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = sys.FUTEX_EMPTY_ADDR; state.stepsSinceLastContextSwitch = sys.SCHED_QUANTUM; finalizeThreadingState(threading, state); @@ -832,7 +845,7 @@ contract MIPS2_Test is CommonTest { threading.left().pop(); threading.right().push(threadB); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -844,7 +857,7 @@ contract MIPS2_Test is CommonTest { /// @dev Static unit test asserting thread left traversal without wakeups function test_threadTraverseLeft_succeeds() public { - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = sys.FUTEX_EMPTY_ADDR; state.step = 10; state.stepsSinceLastContextSwitch = 0; @@ -857,7 +870,7 @@ contract MIPS2_Test is CommonTest { // Create a few threads for (uint256 i = 0; i < 3; i++) { - MIPS2.ThreadState memory thread = threading.createThread(); + IMIPS2.ThreadState memory thread = threading.createThread(); thread.pc = pc; thread.nextPC = pc + 4; thread.futexAddr = sys.FUTEX_EMPTY_ADDR; @@ -868,7 +881,7 @@ contract MIPS2_Test is CommonTest { // Traverse left for (uint256 i = 0; i < 3; i++) { - MIPS2.ThreadState memory currentThread = threading.current(); + IMIPS2.ThreadState memory currentThread = threading.current(); bytes memory threadWitness = threading.witness(); // thread stack updates @@ -879,7 +892,7 @@ contract MIPS2_Test is CommonTest { threading.left().pop(); threading.right().push(currentThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -896,7 +909,7 @@ contract MIPS2_Test is CommonTest { function test_threadTraverseRight_succeeds() public { threading.setTraverseRight(true); - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = sys.FUTEX_EMPTY_ADDR; state.step = 10; state.stepsSinceLastContextSwitch = 0; @@ -910,7 +923,7 @@ contract MIPS2_Test is CommonTest { // Create a few threads for (uint256 i = 0; i < 3; i++) { - MIPS2.ThreadState memory thread = threading.createThread(); + IMIPS2.ThreadState memory thread = threading.createThread(); thread.pc = pc; thread.nextPC = pc + 4; thread.futexAddr = sys.FUTEX_EMPTY_ADDR; @@ -920,7 +933,7 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); for (uint256 i = 0; i < 3; i++) { - MIPS2.ThreadState memory currentThread = threading.current(); + IMIPS2.ThreadState memory currentThread = threading.current(); bytes memory threadWitness = threading.witness(); // thread stack updates @@ -931,7 +944,7 @@ contract MIPS2_Test is CommonTest { threading.right().pop(); threading.left().push(currentThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -948,12 +961,12 @@ contract MIPS2_Test is CommonTest { function test_wakeupPreemptsThread_succeeds() public { threading.createThread(); threading.createThread(); - MIPS2.ThreadState memory threadB = threading.current(); + IMIPS2.ThreadState memory threadB = threading.current(); threadB.futexAddr = 0xdead; threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = 0xabba; finalizeThreadingState(threading, state); @@ -961,7 +974,7 @@ contract MIPS2_Test is CommonTest { threading.left().pop(); threading.right().push(threadB); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -973,7 +986,7 @@ contract MIPS2_Test is CommonTest { /// @dev Static unit test asserting successful wakeup traversal when no threads are ready to wake function test_threadWakeupFullTraversalNoWakeup_succeeds() public { - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = 0x1000; state.step = 10; state.stepsSinceLastContextSwitch = 10; @@ -981,7 +994,7 @@ contract MIPS2_Test is CommonTest { // Create a few threads that are not waiting to wake for (uint256 i = 0; i < 3; i++) { - MIPS2.ThreadState memory thread = threading.createThread(); + IMIPS2.ThreadState memory thread = threading.createThread(); thread.futexAddr = sys.FUTEX_EMPTY_ADDR; threading.replaceCurrent(thread); } @@ -989,7 +1002,7 @@ contract MIPS2_Test is CommonTest { // Traverse left for (uint256 i = 0; i < 3; i++) { - MIPS2.ThreadState memory currentThread = threading.current(); + IMIPS2.ThreadState memory currentThread = threading.current(); bytes memory memProof; (state.memRoot, memProof) = ffi.getCannonMemoryProof(currentThread.pc, 0); @@ -999,7 +1012,7 @@ contract MIPS2_Test is CommonTest { threading.left().pop(); threading.right().push(currentThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -1014,7 +1027,7 @@ contract MIPS2_Test is CommonTest { // Traverse right threading.setTraverseRight(true); for (uint256 i = 0; i < 3; i++) { - MIPS2.ThreadState memory currentThread = threading.current(); + IMIPS2.ThreadState memory currentThread = threading.current(); bytes memory memProof; (state.memRoot, memProof) = ffi.getCannonMemoryProof(currentThread.pc, 0); @@ -1024,7 +1037,7 @@ contract MIPS2_Test is CommonTest { threading.right().pop(); threading.left().push(currentThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -1046,8 +1059,8 @@ contract MIPS2_Test is CommonTest { /// This occurs during wakeup traversal function test_wakeup_traversalEnds_succeeds() public { threading.setTraverseRight(true); - MIPS2.ThreadState memory thread = threading.createThread(); - MIPS2.State memory state; + IMIPS2.ThreadState memory thread = threading.createThread(); + IMIPS2.State memory state; state.traverseRight = true; state.wakeup = 0x1000; state.stepsSinceLastContextSwitch = 10; @@ -1057,7 +1070,7 @@ contract MIPS2_Test is CommonTest { // state changes threading.right().pop(); threading.left().push(thread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; // Note that this does not change. The next thread scheduled (on the left stack) was the last thread on the // right stack. @@ -1075,21 +1088,21 @@ contract MIPS2_Test is CommonTest { function test_futexTimeoutCompletion_succeeds() public { threading.createThread(); threading.createThread(); - MIPS2.ThreadState memory threadB = threading.current(); + IMIPS2.ThreadState memory threadB = threading.current(); threadB.futexAddr = 0x1000; threadB.futexVal = 0xdead; threadB.futexTimeoutStep = 10; threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = sys.FUTEX_EMPTY_ADDR; state.step = 10; state.stepsSinceLastContextSwitch = 10; // must be unchanged finalizeThreadingState(threading, state); // Resume the current blocked thread on futex timeout - MIPS2.ThreadState memory expectThread = copyThread(threadB); + IMIPS2.ThreadState memory expectThread = copyThread(threadB); expectThread.pc = threadB.nextPC; expectThread.nextPC = threadB.nextPC + 4; expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR; @@ -1098,7 +1111,7 @@ contract MIPS2_Test is CommonTest { expectThread.registers[2] = sys.SYS_ERROR_SIGNAL; expectThread.registers[7] = sys.ETIMEDOUT; threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.wakeup = sys.FUTEX_EMPTY_ADDR; finalizeThreadingState(threading, expect); @@ -1121,7 +1134,7 @@ contract MIPS2_Test is CommonTest { threading.createThread(); threading.createThread(); - MIPS2.ThreadState memory threadB = threading.current(); + IMIPS2.ThreadState memory threadB = threading.current(); threadB.futexAddr = _wakeup; threadB.futexVal = _futexVal; @@ -1134,7 +1147,7 @@ contract MIPS2_Test is CommonTest { threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; bytes memory memProof; // unused state.wakeup = _wakeup; state.step = 10; @@ -1142,11 +1155,11 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // Resume the current thread that is blocked - MIPS2.ThreadState memory expectThread = copyThread(threadB); + IMIPS2.ThreadState memory expectThread = copyThread(threadB); // no changes on thread since we're in wakeup traversal threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.wakeup = sys.FUTEX_EMPTY_ADDR; finalizeThreadingState(threading, expect); @@ -1170,7 +1183,7 @@ contract MIPS2_Test is CommonTest { threading.createThread(); threading.createThread(); - MIPS2.ThreadState memory threadB = threading.current(); + IMIPS2.ThreadState memory threadB = threading.current(); threadB.futexAddr = _futexAddr; threadB.futexVal = _futexVal; threadB.futexTimeoutStep = _futexTimeoutStep; @@ -1179,7 +1192,7 @@ contract MIPS2_Test is CommonTest { threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; bytes memory memProof; // unused state.wakeup = _wakeup; state.step = 10; @@ -1187,13 +1200,13 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // state changes - MIPS2.ThreadState memory expectThread = copyThread(threadB); + IMIPS2.ThreadState memory expectThread = copyThread(threadB); // thread internal state is unchanged since we're in wakeup traversal threading.replaceCurrent(expectThread); threading.left().pop(); threading.right().push(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -1206,14 +1219,14 @@ contract MIPS2_Test is CommonTest { function test_futexNoTimeoutCompletion_succeeds() public { threading.createThread(); threading.createThread(); - MIPS2.ThreadState memory threadB = threading.current(); + IMIPS2.ThreadState memory threadB = threading.current(); threadB.futexAddr = 0x1000; threadB.futexVal = 0xdead; threadB.futexTimeoutStep = 100; threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; bytes memory memProof; (state.memRoot, memProof) = ffi.getCannonMemoryProof(0, 0, threadB.futexAddr, threadB.futexVal + 1); state.wakeup = sys.FUTEX_EMPTY_ADDR; @@ -1222,7 +1235,7 @@ contract MIPS2_Test is CommonTest { finalizeThreadingState(threading, state); // Resume the current thread that is blocked - MIPS2.ThreadState memory expectThread = copyThread(threadB); + IMIPS2.ThreadState memory expectThread = copyThread(threadB); expectThread.pc = threadB.nextPC; expectThread.nextPC = threadB.nextPC + 4; expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR; @@ -1232,7 +1245,7 @@ contract MIPS2_Test is CommonTest { expectThread.registers[7] = 0; // errno threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.wakeup = sys.FUTEX_EMPTY_ADDR; finalizeThreadingState(threading, expect); @@ -1245,14 +1258,14 @@ contract MIPS2_Test is CommonTest { function test_futexNoTimeoutPreemptsThread_succeeds() public { threading.createThread(); threading.createThread(); - MIPS2.ThreadState memory threadB = threading.current(); + IMIPS2.ThreadState memory threadB = threading.current(); threadB.futexAddr = 0x1000; threadB.futexVal = 0xdead; threadB.futexTimeoutStep = sys.FUTEX_NO_TIMEOUT; threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; bytes memory memProof; (state.memRoot, memProof) = ffi.getCannonMemoryProof(0, 0, threadB.futexAddr, threadB.futexVal); state.wakeup = sys.FUTEX_EMPTY_ADDR; @@ -1262,7 +1275,7 @@ contract MIPS2_Test is CommonTest { // Expect the thread to be moved from the left to right stack threading.left().pop(); threading.right().push(threadB); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = 0; finalizeThreadingState(threading, expect); @@ -1275,19 +1288,19 @@ contract MIPS2_Test is CommonTest { function test_threadExit_succeeds() public { threading.createThread(); threading.createThread(); - MIPS2.ThreadState memory threadB = threading.current(); + IMIPS2.ThreadState memory threadB = threading.current(); threadB.exited = true; threading.replaceCurrent(threadB); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = sys.FUTEX_EMPTY_ADDR; state.stepsSinceLastContextSwitch = 10; finalizeThreadingState(threading, state); // Expect the thread to be popped from the left stack threading.left().pop(); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.stepsSinceLastContextSwitch = 0; expect.step = state.step + 1; finalizeThreadingState(threading, expect); @@ -1303,18 +1316,18 @@ contract MIPS2_Test is CommonTest { threading.createThread(); threading.setTraverseRight(false); threading.createThread(); - MIPS2.ThreadState memory threadL = threading.current(); + IMIPS2.ThreadState memory threadL = threading.current(); threadL.exited = true; threading.replaceCurrent(threadL); bytes memory threadWitness = threading.witness(); - MIPS2.State memory state; + IMIPS2.State memory state; state.wakeup = sys.FUTEX_EMPTY_ADDR; state.stepsSinceLastContextSwitch = 10; finalizeThreadingState(threading, state); threading.left().pop(); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.stepsSinceLastContextSwitch = 0; expect.step = state.step + 1; expect.traverseRight = true; @@ -1324,9 +1337,9 @@ contract MIPS2_Test is CommonTest { assertEq(postState, outputState(expect), "unexpected post state"); } - function test_mmap_succeeds_simple() external { + function test_mmap_simple_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); state.heap = 4096; @@ -1340,8 +1353,8 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); bytes memory encodedState = encodeState(state); - MIPS2.State memory expect = copyState(state); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.State memory expect = copyState(state); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expect.memRoot = state.memRoot; expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; @@ -1356,9 +1369,9 @@ contract MIPS2_Test is CommonTest { assertEq(postState, outputState(expect), "unexpected post state"); } - function test_mmap_succeeds_justWithinMemLimit() external { + function test_mmap_justWithinMemLimit_succeeds() external { uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); state.heap = sys.HEAP_END - 4096; // Set up to increase heap to its limit @@ -1372,8 +1385,8 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); bytes memory encodedState = encodeState(state); - MIPS2.State memory expect = copyState(state); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.State memory expect = copyState(state); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expect.memRoot = state.memRoot; expect.step += 1; expect.stepsSinceLastContextSwitch += 1; @@ -1388,9 +1401,9 @@ contract MIPS2_Test is CommonTest { assertEq(postState, outputState(expect), "unexpected post state"); } - function test_mmap_fails() external { + function test_step_mmap_fails() external { uint32 insn = 0x0000000c; // syscall - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); state.heap = sys.HEAP_END - 4096; // Set up to increase heap beyond its limit @@ -1404,8 +1417,8 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); bytes memory encodedState = encodeState(state); - MIPS2.State memory expect = copyState(state); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.State memory expect = copyState(state); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expect.memRoot = state.memRoot; expect.step += 1; expect.stepsSinceLastContextSwitch += 1; @@ -1423,7 +1436,7 @@ contract MIPS2_Test is CommonTest { function test_srav_succeeds() external { uint32 insn = encodespec(0xa, 0x9, 0x8, 7); // srav t0, t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0xdeafbeef; // t1 thread.registers[10] = 12; // t2 @@ -1433,8 +1446,8 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); bytes memory encodedState = encodeState(state); - MIPS2.State memory expect = copyState(state); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.State memory expect = copyState(state); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expect.memRoot = state.memRoot; expect.step += 1; expect.stepsSinceLastContextSwitch += 1; @@ -1457,7 +1470,7 @@ contract MIPS2_Test is CommonTest { _rs = uint32(bound(uint256(_rs), 0x20, type(uint32).max)); uint32 insn = encodespec(0xa, 0x9, 0x8, 7); // srav t0, t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0xdeadbeef; // t1 thread.registers[10] = _rs; // t2 @@ -1470,8 +1483,8 @@ contract MIPS2_Test is CommonTest { // Calculate shamt uint32 shamt = thread.registers[10] & 0x1F; - MIPS2.State memory expect = copyState(state); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.State memory expect = copyState(state); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expect.memRoot = state.memRoot; expect.step += 1; expect.stepsSinceLastContextSwitch += 1; @@ -1486,7 +1499,7 @@ contract MIPS2_Test is CommonTest { function test_add_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x20); // add t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 12; thread.registers[18] = 20; @@ -1494,7 +1507,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] + thread.registers[18]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1502,7 +1515,7 @@ contract MIPS2_Test is CommonTest { function test_addu_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x21); // addu t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 12; thread.registers[18] = 20; @@ -1510,7 +1523,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] + thread.registers[18]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1519,14 +1532,14 @@ contract MIPS2_Test is CommonTest { function test_addi_succeeds() public { uint16 imm = 40; uint32 insn = encodeitype(0x8, 17, 8, imm); // addi t0, s1, 40 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 1; // t0 thread.registers[17] = 4; // t1 updateThreadStacks(state, thread); uint32 result = thread.registers[17] + imm; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 @@ -1537,7 +1550,7 @@ contract MIPS2_Test is CommonTest { function test_addiSign_succeeds() public { uint16 imm = 0xfffe; // -2 uint32 insn = encodeitype(0x8, 17, 8, imm); // addi t0, s1, 40 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 1; // s0 thread.registers[17] = 2; // s1 @@ -1545,7 +1558,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = 0; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1555,14 +1568,14 @@ contract MIPS2_Test is CommonTest { // copy the existing corresponding test in MIPS.t.sol and adapt for MIPS2 uint16 imm = 40; uint32 insn = encodeitype(0x9, 17, 8, imm); // addui t0, s1, 40 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 1; // t0 thread.registers[17] = 4; // t1 updateThreadStacks(state, thread); uint32 result = thread.registers[17] + imm; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 @@ -1572,7 +1585,7 @@ contract MIPS2_Test is CommonTest { function test_sub_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x22); // sub t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 20; thread.registers[18] = 12; @@ -1580,7 +1593,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] - thread.registers[18]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1588,7 +1601,7 @@ contract MIPS2_Test is CommonTest { function test_subu_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x23); // subu t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 20; thread.registers[18] = 12; @@ -1596,7 +1609,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] - thread.registers[18]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1604,7 +1617,7 @@ contract MIPS2_Test is CommonTest { function test_and_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x24); // and t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 1200; thread.registers[18] = 490; @@ -1612,7 +1625,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] & thread.registers[18]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1621,7 +1634,7 @@ contract MIPS2_Test is CommonTest { function test_andi_succeeds() public { uint16 imm = 40; uint32 insn = encodeitype(0xc, 17, 8, imm); // andi t0, s1, 40 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 1; // t0 thread.registers[17] = 4; // s1 @@ -1629,7 +1642,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] & imm; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1637,7 +1650,7 @@ contract MIPS2_Test is CommonTest { function test_or_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x25); // or t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 1200; thread.registers[18] = 490; @@ -1645,7 +1658,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] | thread.registers[18]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1654,7 +1667,7 @@ contract MIPS2_Test is CommonTest { function test_ori_succeeds() public { uint16 imm = 40; uint32 insn = encodeitype(0xd, 17, 8, imm); // ori t0, s1, 40 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 1; // t0 thread.registers[17] = 4; // s1 @@ -1662,7 +1675,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] | imm; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1670,7 +1683,7 @@ contract MIPS2_Test is CommonTest { function test_xor_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x26); // xor t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 1200; thread.registers[18] = 490; @@ -1678,7 +1691,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] ^ thread.registers[18]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1687,7 +1700,7 @@ contract MIPS2_Test is CommonTest { function test_xori_succeeds() public { uint16 imm = 40; uint32 insn = encodeitype(0xe, 17, 8, imm); // xori t0, s1, 40 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 1; // t0 thread.registers[17] = 4; // s1 @@ -1695,7 +1708,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] ^ imm; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1703,7 +1716,7 @@ contract MIPS2_Test is CommonTest { function test_nor_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x27); // nor t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 1200; thread.registers[18] = 490; @@ -1711,7 +1724,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = ~(thread.registers[17] | thread.registers[18]); // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1719,7 +1732,7 @@ contract MIPS2_Test is CommonTest { function test_slt_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x2a); // slt t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 0xFF_FF_FF_FE; // -2 thread.registers[18] = 5; @@ -1727,7 +1740,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = 1; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1747,7 +1760,7 @@ contract MIPS2_Test is CommonTest { function test_sltu_succeeds() public { uint32 insn = encodespec(17, 18, 8, 0x2b); // sltu t0, s1, s2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[17] = 1200; thread.registers[18] = 490; @@ -1755,7 +1768,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[17] < thread.registers[18] ? 1 : 0; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1764,7 +1777,7 @@ contract MIPS2_Test is CommonTest { function test_lb_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x20, 0x9, 0x8, 0x4); // lb $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0x12_00_00_00); thread.registers[8] = 0; // t0 thread.registers[9] = t1; @@ -1772,7 +1785,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = 0x12; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1782,7 +1795,7 @@ contract MIPS2_Test is CommonTest { uint32 t1 = 0x100; uint32 val = 0x12_23_00_00; uint32 insn = encodeitype(0x21, 0x9, 0x8, 0x4); // lh $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, val); thread.registers[8] = 0; // t0 thread.registers[9] = t1; @@ -1790,7 +1803,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = 0x12_23; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1800,7 +1813,7 @@ contract MIPS2_Test is CommonTest { uint32 t1 = 0x100; uint32 val = 0x12_23_45_67; uint32 insn = encodeitype(0x23, 0x9, 0x8, 0x4); // lw $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, val); thread.registers[8] = 0; // t0 thread.registers[9] = t1; @@ -1808,7 +1821,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = val; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1817,7 +1830,7 @@ contract MIPS2_Test is CommonTest { function test_lbu_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x24, 0x9, 0x8, 0x4); // lbu $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0x12_23_00_00); thread.registers[8] = 0; // t0 thread.registers[9] = t1; @@ -1825,7 +1838,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = 0x12; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1834,7 +1847,7 @@ contract MIPS2_Test is CommonTest { function test_lhu_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x25, 0x9, 0x8, 0x4); // lhu $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0x12_23_00_00); thread.registers[8] = 0; // t0 thread.registers[9] = t1; @@ -1842,7 +1855,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = 0x12_23; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1851,7 +1864,7 @@ contract MIPS2_Test is CommonTest { function test_lwl_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x22, 0x9, 0x8, 0x4); // lwl $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; @@ -1859,7 +1872,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); thread.registers[8] = 0x12_34_56_78; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1868,7 +1881,7 @@ contract MIPS2_Test is CommonTest { function test_lwl_unaligned_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x22, 0x9, 0x8, 0x5); // lwl $t0, 5($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); thread.registers[8] = 0x34_56_78_dd; // t0 thread.registers[9] = t1; // t0 @@ -1876,7 +1889,7 @@ contract MIPS2_Test is CommonTest { (state.memRoot, memProof) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0x12_34_56_78); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); } @@ -1884,7 +1897,7 @@ contract MIPS2_Test is CommonTest { function test_lwr_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x26, 0x9, 0x8, 0x4); // lwr $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; @@ -1892,7 +1905,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); thread.registers[8] = 0xaa_bb_cc_12; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1901,7 +1914,7 @@ contract MIPS2_Test is CommonTest { function test_lwr_unaligned_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x26, 0x9, 0x8, 0x5); // lwr $t0, 5($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0x12_34_56_78); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; @@ -1910,7 +1923,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); thread.registers[8] = 0xaa_bb_12_34; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -1920,14 +1933,14 @@ contract MIPS2_Test is CommonTest { uint32 t1 = 0x100; uint32 insn = encodeitype(0x28, 0x9, 0x8, 0x4); // sb $t0, 4($t1) // note. cannon memory is zero-initialized. mem[t+4] = 0 is a no-op - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xdd_00_00_00); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); @@ -1937,14 +1950,14 @@ contract MIPS2_Test is CommonTest { function test_sh_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x29, 0x9, 0x8, 0x4); // sh $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xcc_dd_00_00); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); @@ -1954,14 +1967,14 @@ contract MIPS2_Test is CommonTest { function test_swl_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x2a, 0x9, 0x8, 0x4); // swl $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); @@ -1971,14 +1984,14 @@ contract MIPS2_Test is CommonTest { function test_sw_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x2b, 0x9, 0x8, 0x4); // sw $t0, 4($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); @@ -1988,14 +2001,14 @@ contract MIPS2_Test is CommonTest { function test_swr_succeeds() public { uint32 t1 = 0x100; uint32 insn = encodeitype(0x2e, 0x9, 0x8, 0x5); // swr $t0, 5($t1) - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, t1 + 4, 0); thread.registers[8] = 0xaa_bb_cc_dd; // t0 thread.registers[9] = t1; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xcc_dd_00_00); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); @@ -2009,15 +2022,15 @@ contract MIPS2_Test is CommonTest { uint32 effAddr = base + offset; uint32 insn = encodeitype(0x30, 0x9, 0x8, offset); // ll baseReg, rtReg, offset - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, effAddr, memVal); thread.registers[8] = 0; // rtReg thread.registers[9] = base; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal); - expect.llReservationActive = true; + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal); + expect.llReservationStatus = 1; expect.llAddress = effAddr; expect.llOwnerThread = thread.threadID; @@ -2032,9 +2045,9 @@ contract MIPS2_Test is CommonTest { uint32 writeMemVal = 0xaa_bb_cc_dd; uint32 insn = encodeitype(0x38, 0x9, 0x8, offset); // ll baseReg, rtReg, offset - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, effAddr, 0); - state.llReservationActive = true; + state.llReservationStatus = 1; state.llAddress = effAddr; state.llOwnerThread = thread.threadID; thread.registers[8] = writeMemVal; @@ -2042,9 +2055,9 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, effAddr, writeMemVal); - expect.llReservationActive = false; + expect.llReservationStatus = 0; expect.llAddress = 0; expect.llOwnerThread = 0; @@ -2054,7 +2067,7 @@ contract MIPS2_Test is CommonTest { function test_movn_succeeds() public { uint32 insn = encodespec(0x9, 0xa, 0x8, 0xb); // movn $t0, $t1, $t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xa; // t0 thread.registers[9] = 0xb; // t1 @@ -2063,7 +2076,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[9]; // t1 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2077,7 +2090,7 @@ contract MIPS2_Test is CommonTest { function test_movz_succeeds() public { uint32 insn = encodespec(0x9, 0xa, 0x8, 0xa); // movz $t0, $t1, $t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xa; // t0 thread.registers[9] = 0xb; // t1 @@ -2086,7 +2099,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[9]; // t1 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2100,13 +2113,13 @@ contract MIPS2_Test is CommonTest { function test_mflo_succeeds() public { uint32 insn = encodespec(0x0, 0x0, 0x8, 0x12); // mflo $t0 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.lo = 0xdeadbeef; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.lo); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.lo); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2114,13 +2127,13 @@ contract MIPS2_Test is CommonTest { function test_mfhi_succeeds() public { uint32 insn = encodespec(0x0, 0x0, 0x8, 0x10); // mfhi $t0 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.hi = 0xdeadbeef; bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.hi); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.hi); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2128,14 +2141,14 @@ contract MIPS2_Test is CommonTest { function test_mthi_succeeds() public { uint32 insn = encodespec(0x8, 0x0, 0x0, 0x11); // mthi $t0 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xdeadbeef; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); thread.hi = thread.registers[8]; - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2143,14 +2156,14 @@ contract MIPS2_Test is CommonTest { function test_mtlo_succeeds() public { uint32 insn = encodespec(0x8, 0x0, 0x0, 0x13); // mtlo $t0 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xdeadbeef; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); thread.lo = thread.registers[8]; - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2158,7 +2171,7 @@ contract MIPS2_Test is CommonTest { function test_mul_succeeds() public { uint32 insn = encodespec2(0x9, 0xa, 0x8, 0x2); // mul t0, t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 5; // t1 thread.registers[10] = 2; // t2 @@ -2166,7 +2179,7 @@ contract MIPS2_Test is CommonTest { updateThreadStacks(state, thread); uint32 result = thread.registers[9] * thread.registers[10]; // t0 - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2174,7 +2187,7 @@ contract MIPS2_Test is CommonTest { function test_mult_succeeds() public { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x18); // mult t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x0F_FF_00_00; // t1 thread.registers[10] = 100; // t2 @@ -2185,7 +2198,7 @@ contract MIPS2_Test is CommonTest { uint32 hiResult = 0x6; thread.lo = loResult; thread.hi = hiResult; - MIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 + IMIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2193,7 +2206,7 @@ contract MIPS2_Test is CommonTest { function test_multu_succeeds() public { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x19); // multu t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x0F_FF_00_00; // t1 thread.registers[10] = 100; // t2 @@ -2204,7 +2217,7 @@ contract MIPS2_Test is CommonTest { uint32 hiResult = 0x6; thread.lo = loResult; thread.hi = hiResult; - MIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 + IMIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2212,7 +2225,7 @@ contract MIPS2_Test is CommonTest { function test_div_succeeds() public { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1a); // div t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 5; // t1 thread.registers[10] = 2; // t2 @@ -2221,7 +2234,7 @@ contract MIPS2_Test is CommonTest { thread.lo = 2; thread.hi = 1; - MIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 + IMIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2229,7 +2242,7 @@ contract MIPS2_Test is CommonTest { function test_divu_succeeds() public { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1b); // divu t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 5; // t1 thread.registers[10] = 2; // t2 @@ -2238,7 +2251,7 @@ contract MIPS2_Test is CommonTest { thread.lo = 2; thread.hi = 1; - MIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 + IMIPS2.State memory expect = arithmeticPostState(state, thread, 0, /* t0 */ 0); // no update on t0 bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2246,7 +2259,7 @@ contract MIPS2_Test is CommonTest { function test_div_byZero_fails() public { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1a); // div t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 5; // t1 thread.registers[10] = 0; // t2 @@ -2259,7 +2272,7 @@ contract MIPS2_Test is CommonTest { function test_divu_byZero_fails() public { uint32 insn = encodespec(0x9, 0xa, 0x0, 0x1b); // divu t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 5; // t1 thread.registers[10] = 0; // t2 @@ -2273,14 +2286,14 @@ contract MIPS2_Test is CommonTest { function test_beq_succeeds() public { uint16 boff = 0x10; uint32 insn = encodeitype(0x4, 0x9, 0x8, boff); // beq $t0, $t1, 16 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xdeadbeef; // t0 thread.registers[9] = 0xdeadbeef; // t1 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); + IMIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2289,14 +2302,14 @@ contract MIPS2_Test is CommonTest { function test_beq_notTaken_succeeds() public { uint16 boff = 0x10; uint32 insn = encodeitype(0x4, 0x9, 0x8, boff); // beq $t0, $t1, 16 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xaa; // t0 thread.registers[9] = 0xdeadbeef; // t1 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + 4); + IMIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + 4); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2305,14 +2318,14 @@ contract MIPS2_Test is CommonTest { function test_bne_succeeds() public { uint16 boff = 0x10; uint32 insn = encodeitype(0x5, 0x9, 0x8, boff); // bne $t0, $t1, 16 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xdeadbeef; // t0 thread.registers[9] = 0xaa; // t1 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); + IMIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2321,13 +2334,13 @@ contract MIPS2_Test is CommonTest { function test_blez_succeeds() public { uint16 boff = 0x10; uint32 insn = encodeitype(0x6, 0x8, 0x0, boff); // blez $t0, 16 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); + IMIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2336,13 +2349,13 @@ contract MIPS2_Test is CommonTest { function test_bgtz_succeeds() public { uint16 boff = 0xa0; uint32 insn = encodeitype(0x7, 0x8, 0x0, boff); // bgtz $t0, 0xa0 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 1; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); + IMIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2351,13 +2364,13 @@ contract MIPS2_Test is CommonTest { function test_bltz_succeeds() public { uint16 boff = 0x10; uint32 insn = encodeitype(0x1, 0x8, 0x0, boff); // bltz $t0, 16 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0xF0_00_00_00; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); + IMIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2366,13 +2379,13 @@ contract MIPS2_Test is CommonTest { function test_bgez_succeeds() public { uint16 boff = 0x10; uint32 insn = encodeitype(0x1, 0x8, 0x1, boff); // bgez $t0, 16 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = 0x00_00_00_01; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); + IMIPS2.State memory expect = controlFlowPostState(state, thread, thread.nextPC + (uint32(boff) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2381,12 +2394,12 @@ contract MIPS2_Test is CommonTest { function test_jump_succeeds() public { uint32 label = 0x02_00_00_02; // set the 26th bit to assert no sign extension uint32 insn = uint32(0x08_00_00_00) | label; // j label - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, label << 2); + IMIPS2.State memory expect = controlFlowPostState(state, thread, label << 2); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2396,12 +2409,12 @@ contract MIPS2_Test is CommonTest { uint32 pcRegion1 = 0x10000000; uint32 label = 0x2; uint32 insn = uint32(0x08_00_00_00) | label; // j label - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(pcRegion1, insn, 0x4, 0); bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = + IMIPS2.State memory expect = controlFlowPostState(state, thread, (thread.nextPC & 0xF0_00_00_00) | (uint32(label) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); @@ -2411,13 +2424,13 @@ contract MIPS2_Test is CommonTest { function test_jal_succeeds() public { uint32 label = 0x02_00_00_02; // set the 26th bit to assert no sign extension uint32 insn = uint32(0x0c_00_00_00) | label; // jal label - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); thread.registers[31] = thread.pc + 8; // ra - MIPS2.State memory expect = controlFlowPostState(state, thread, label << 2); + IMIPS2.State memory expect = controlFlowPostState(state, thread, label << 2); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2427,13 +2440,13 @@ contract MIPS2_Test is CommonTest { uint32 pcRegion1 = 0x10000000; uint32 label = 0x2; uint32 insn = uint32(0x0c_00_00_00) | label; // jal label - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(pcRegion1, insn, 0x4, 0); bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); thread.registers[31] = thread.pc + 8; // ra - MIPS2.State memory expect = + IMIPS2.State memory expect = controlFlowPostState(state, thread, (thread.nextPC & 0xF0_00_00_00) | (uint32(label) << 2)); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); @@ -2443,13 +2456,13 @@ contract MIPS2_Test is CommonTest { function test_jr_succeeds() public { uint16 tgt = 0x34; uint32 insn = encodespec(0x8, 0, 0, 0x8); // jr t0 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = tgt; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); - MIPS2.State memory expect = controlFlowPostState(state, thread, tgt); + IMIPS2.State memory expect = controlFlowPostState(state, thread, tgt); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2458,14 +2471,14 @@ contract MIPS2_Test is CommonTest { function test_jalr_succeeds() public { uint16 tgt = 0x34; uint32 insn = encodespec(0x8, 0, 0x9, 0x9); // jalr t1, t0 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[8] = tgt; // t0 bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT); updateThreadStacks(state, thread); thread.registers[9] = thread.pc + 8; // t1 - MIPS2.State memory expect = controlFlowPostState(state, thread, tgt); + IMIPS2.State memory expect = controlFlowPostState(state, thread, tgt); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); assertEq(postState, outputState(expect), "unexpected post state"); @@ -2474,13 +2487,13 @@ contract MIPS2_Test is CommonTest { function test_sll_succeeds() external { uint8 shiftamt = 4; uint32 insn = encodespec(0x0, 0x9, 0x8, uint16(shiftamt) << 6); // sll t0, t1, 3 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x20; // t1 updateThreadStacks(state, thread); uint32 result = thread.registers[9] << shiftamt; - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 @@ -2491,13 +2504,13 @@ contract MIPS2_Test is CommonTest { function test_srl_succeeds() external { uint8 shiftamt = 4; uint32 insn = encodespec(0x0, 0x9, 0x8, uint16(shiftamt) << 6 | 2); // srl t0, t1, 3 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x20; // t1 updateThreadStacks(state, thread); uint32 result = thread.registers[9] >> shiftamt; - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 @@ -2508,13 +2521,13 @@ contract MIPS2_Test is CommonTest { function test_sra_succeeds() external { uint8 shiftamt = 4; uint32 insn = encodespec(0x0, 0x9, 0x8, uint16(shiftamt) << 6 | 3); // sra t0, t1, 3 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x80_00_00_20; // t1 updateThreadStacks(state, thread); uint32 result = 0xF8_00_00_02; // 4 shifts while preserving sign bit - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 @@ -2524,14 +2537,14 @@ contract MIPS2_Test is CommonTest { function test_sllv_succeeds() external { uint32 insn = encodespec(0xa, 0x9, 0x8, 4); // sllv t0, t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x20; // t1 thread.registers[10] = 4; // t2 updateThreadStacks(state, thread); uint32 result = thread.registers[9] << thread.registers[10]; - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 @@ -2541,14 +2554,14 @@ contract MIPS2_Test is CommonTest { function test_srlv_succeeds() external { uint32 insn = encodespec(0xa, 0x9, 0x8, 6); // srlv t0, t1, t2 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x20_00; // t1 thread.registers[10] = 4; // t2 updateThreadStacks(state, thread); uint32 result = thread.registers[9] >> thread.registers[10]; - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ result); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 @@ -2558,11 +2571,11 @@ contract MIPS2_Test is CommonTest { function test_lui_succeeds() external { uint32 insn = encodeitype(0xf, 0x0, 0x8, 0x4); // lui $t0, 0x04 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ 0x00_04_00_00); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ 0x00_04_00_00); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 ); @@ -2571,12 +2584,12 @@ contract MIPS2_Test is CommonTest { function test_clo_succeeds() external { uint32 insn = encodespec2(0x9, 0x0, 0x8, 0x21); // clo t0, t1 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0xFF_00_00_00; // t1 updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ 8); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ 8); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 ); @@ -2585,12 +2598,12 @@ contract MIPS2_Test is CommonTest { function test_clz_succeeds() external { uint32 insn = encodespec2(0x9, 0x0, 0x8, 0x20); // clz t0, t1 - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, 0x4, 0); thread.registers[9] = 0x00_00_F0_00; // t1 updateThreadStacks(state, thread); - MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ 16); + IMIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ 16); bytes32 postState = mips.step( encodeState(state), bytes.concat(abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT), memProof), 0 ); @@ -2602,7 +2615,7 @@ contract MIPS2_Test is CommonTest { uint32 insn = 0x0000000c; // syscall uint32 a1 = 0x4; uint32 a1_val = 0x0000abba; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, a1, a1_val); state.preimageKey = bytes32(uint256(1) << 248 | 0x01); state.preimageOffset = 8; // start reading past the pre-image length prefix @@ -2615,7 +2628,7 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = threading.witness(); finalizeThreadingState(threading, state); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 4; // return @@ -2628,7 +2641,7 @@ contract MIPS2_Test is CommonTest { uint8 partOffset = 8; oracle.loadLocalData(uint256(state.preimageKey), 0, word, size, partOffset); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.preimageOffset += 4; expect.step = state.step + 1; expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; @@ -2644,7 +2657,7 @@ contract MIPS2_Test is CommonTest { uint32 insn = 0x0000000c; // syscall uint32 a1 = 0x4; uint32 a1_val = 0x0000abba; - (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = + (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = constructMIPSState(0, insn, a1, a1_val); state.preimageKey = bytes32(0); state.preimageOffset = 1; @@ -2657,14 +2670,14 @@ contract MIPS2_Test is CommonTest { bytes memory threadWitness = threading.witness(); finalizeThreadingState(threading, state); - MIPS2.ThreadState memory expectThread = copyThread(thread); + IMIPS2.ThreadState memory expectThread = copyThread(thread); expectThread.pc = thread.nextPC; expectThread.nextPC = thread.nextPC + 4; expectThread.registers[2] = 4; // return expectThread.registers[7] = 0; // errno threading.replaceCurrent(expectThread); - MIPS2.State memory expect = copyState(state); + IMIPS2.State memory expect = copyState(state); expect.preimageKey = bytes32(uint256(0xabba)); expect.preimageOffset = 0; expect.step = state.step + 1; @@ -2676,7 +2689,7 @@ contract MIPS2_Test is CommonTest { } /// @dev Modifies the MIPS2 State based on threading state - function finalizeThreadingState(Threading _threading, MIPS2.State memory _state) internal view { + function finalizeThreadingState(Threading _threading, IMIPS2.State memory _state) internal view { _state.leftThreadStack = _threading.left().root(); _state.rightThreadStack = _threading.right().root(); _state.nextThreadID = uint32(_threading.nextThreadID()); @@ -2690,7 +2703,7 @@ contract MIPS2_Test is CommonTest { uint32 val ) internal - returns (MIPS2.State memory state_, MIPS2.ThreadState memory thread_, bytes memory proof_) + returns (IMIPS2.State memory state_, IMIPS2.ThreadState memory thread_, bytes memory proof_) { (state_.memRoot, proof_) = ffi.getCannonMemoryProof(pc, insn, addr, val); state_.nextThreadID = 1; @@ -2703,7 +2716,7 @@ contract MIPS2_Test is CommonTest { } /// @dev Updates the state stack roots with a single thread - function updateThreadStacks(MIPS2.State memory _state, MIPS2.ThreadState memory _thread) internal pure { + function updateThreadStacks(IMIPS2.State memory _state, IMIPS2.ThreadState memory _thread) internal pure { if (_state.traverseRight) { _state.rightThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(_thread)))); } else { @@ -2713,16 +2726,16 @@ contract MIPS2_Test is CommonTest { /// @dev Constructs a post-state after an arithmetic or logical instruction function arithmeticPostState( - MIPS2.State memory _state, - MIPS2.ThreadState memory _thread, + IMIPS2.State memory _state, + IMIPS2.ThreadState memory _thread, uint32 reg, uint32 regVal ) internal pure - returns (MIPS2.State memory out_) + returns (IMIPS2.State memory out_) { - MIPS2.ThreadState memory expectThread = copyThread(_thread); + IMIPS2.ThreadState memory expectThread = copyThread(_thread); expectThread.pc = _thread.nextPC; expectThread.nextPC = _thread.nextPC + 4; expectThread.registers[reg] = regVal; @@ -2735,15 +2748,15 @@ contract MIPS2_Test is CommonTest { /// @dev Constructs a post-state after a branch instruction function controlFlowPostState( - MIPS2.State memory _state, - MIPS2.ThreadState memory _thread, + IMIPS2.State memory _state, + IMIPS2.ThreadState memory _thread, uint32 branchTarget ) internal pure - returns (MIPS2.State memory out_) + returns (IMIPS2.State memory out_) { - MIPS2.ThreadState memory expectThread = copyThread(_thread); + IMIPS2.ThreadState memory expectThread = copyThread(_thread); expectThread.pc = _thread.nextPC; expectThread.nextPC = branchTarget; @@ -2753,42 +2766,32 @@ contract MIPS2_Test is CommonTest { out_.leftThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(expectThread)))); } - function encodeState(MIPS2.State memory _state) internal pure returns (bytes memory) { - // Split up encoding to get around stack-too-deep error - return abi.encodePacked(encodeStateA(_state), encodeStateB(_state)); - } - - function encodeStateA(MIPS2.State memory _state) internal pure returns (bytes memory) { - return abi.encodePacked( + function encodeState(IMIPS2.State memory _state) internal pure returns (bytes memory) { + bytes memory a = abi.encodePacked( _state.memRoot, _state.preimageKey, _state.preimageOffset, _state.heap, - _state.llReservationActive, - _state.llAddress, - _state.llOwnerThread, - _state.exitCode, - _state.exited, - _state.step, - _state.stepsSinceLastContextSwitch, - _state.wakeup, - _state.traverseRight, - _state.leftThreadStack + _state.llReservationStatus, + _state.llAddress ); + bytes memory b = abi.encodePacked( + _state.llOwnerThread, _state.exitCode, _state.exited, _state.step, _state.stepsSinceLastContextSwitch + ); + bytes memory c = abi.encodePacked( + _state.wakeup, _state.traverseRight, _state.leftThreadStack, _state.rightThreadStack, _state.nextThreadID + ); + return abi.encodePacked(a, b, c); } - function encodeStateB(MIPS2.State memory _state) internal pure returns (bytes memory) { - return abi.encodePacked(_state.rightThreadStack, _state.nextThreadID); - } - - function copyState(MIPS2.State memory _state) internal pure returns (MIPS2.State memory out_) { + function copyState(IMIPS2.State memory _state) internal pure returns (IMIPS2.State memory out_) { bytes memory data = abi.encode(_state); - return abi.decode(data, (MIPS2.State)); + return abi.decode(data, (IMIPS2.State)); } - function copyThread(MIPS2.ThreadState memory _thread) internal pure returns (MIPS2.ThreadState memory out_) { + function copyThread(IMIPS2.ThreadState memory _thread) internal pure returns (IMIPS2.ThreadState memory out_) { bytes memory data = abi.encode(_thread); - return abi.decode(data, (MIPS2.ThreadState)); + return abi.decode(data, (IMIPS2.ThreadState)); } /// @dev MIPS VM status codes: @@ -2796,7 +2799,7 @@ contract MIPS2_Test is CommonTest { /// 1. Exited with success (Invalid) /// 2. Exited with failure (Panic) /// 3. Unfinished - function vmStatus(MIPS2.State memory state) internal pure returns (VMStatus out_) { + function vmStatus(IMIPS2.State memory state) internal pure returns (VMStatus out_) { if (!state.exited) { return VMStatuses.UNFINISHED; } else if (state.exitCode == 0) { @@ -2808,9 +2811,9 @@ contract MIPS2_Test is CommonTest { } } - event ExpectedOutputState(bytes encoded, MIPS2.State state); + event ExpectedOutputState(bytes encoded, IMIPS2.State state); - function outputState(MIPS2.State memory state) internal returns (bytes32 out_) { + function outputState(IMIPS2.State memory state) internal returns (bytes32 out_) { bytes memory enc = encodeState(state); emit ExpectedOutputState(enc, state); VMStatus status = vmStatus(state); @@ -2820,20 +2823,20 @@ contract MIPS2_Test is CommonTest { } } - function encodeitype(uint8 opcode, uint8 rs, uint8 rt, uint16 imm) internal pure returns (uint32 insn) { - insn = uint32(opcode) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | imm; + function encodeitype(uint8 opcode, uint8 rs, uint8 rt, uint16 imm) internal pure returns (uint32 insn_) { + insn_ = uint32(opcode) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | imm; } - function encodespec(uint8 rs, uint8 rt, uint8 rd, uint16 funct) internal pure returns (uint32 insn) { - insn = uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); + function encodespec(uint8 rs, uint8 rt, uint8 rd, uint16 funct) internal pure returns (uint32 insn_) { + insn_ = uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); } - function encodespec2(uint8 rs, uint8 rt, uint8 rd, uint8 funct) internal pure returns (uint32 insn) { - insn = uint32(28) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); + function encodespec2(uint8 rs, uint8 rt, uint8 rd, uint8 funct) internal pure returns (uint32 insn_) { + insn_ = uint32(28) << 26 | uint32(rs) << 21 | uint32(rt) << 16 | uint32(rd) << 11 | uint32(funct); } } -function encodeThread(MIPS2.ThreadState memory _thread) pure returns (bytes memory) { +function encodeThread(IMIPS2.ThreadState memory _thread) pure returns (bytes memory) { bytes memory registers; for (uint256 i = 0; i < _thread.registers.length; i++) { registers = bytes.concat(registers, abi.encodePacked(_thread.registers[i])); diff --git a/packages/contracts-bedrock/test/cannon/MIPS64Memory.t.sol b/packages/contracts-bedrock/test/cannon/MIPS64Memory.t.sol new file mode 100644 index 0000000000000..9ba0f0bcf5f54 --- /dev/null +++ b/packages/contracts-bedrock/test/cannon/MIPS64Memory.t.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { CommonTest } from "test/setup/CommonTest.sol"; +import { MIPS64Memory } from "src/cannon/libraries/MIPS64Memory.sol"; +import { InvalidMemoryProof } from "src/cannon/libraries/CannonErrors.sol"; + +contract MIPS64Memory_Test is CommonTest { + MIPS64MemoryWithCalldata mem; + + error InvalidAddress(); + + function setUp() public virtual override { + super.setUp(); + mem = new MIPS64MemoryWithCalldata(); + } + + /// @dev Static unit test for basic memory access + function test_readMem_succeeds() external { + uint64 addr = 0x100; + uint64 word = 0x11_22_33_44_55_66_77_88; + bytes32 root; + bytes memory proof; + (root, proof) = ffi.getCannonMemory64Proof(addr, word); + uint64 readWord = mem.readMem(root, addr, 0, proof); + assertEq(readWord, word); + } + + /// @dev Static unit test asserting that reading from the zero address succeeds + function test_readMemAtZero_succeeds() external { + uint64 addr = 0x0; + uint64 word = 0x11_22_33_44_55_66_77_88; + bytes32 root; + bytes memory proof; + (root, proof) = ffi.getCannonMemory64Proof(addr, word); + uint64 readWord = mem.readMem(root, addr, 0, proof); + assertEq(readWord, word); + } + + /// @dev Static unit test asserting that reading from high memory area succeeds + function test_readMemHighMem_succeeds() external { + uint64 addr = 0xFF_FF_FF_FF_00_00_00_88; + uint64 word = 0x11_22_33_44_55_66_77_88; + bytes32 root; + bytes memory proof; + (root, proof) = ffi.getCannonMemory64Proof(addr, word); + uint64 readWord = mem.readMem(root, addr, 0, proof); + assertEq(readWord, word); + } + + /// @dev Static unit test asserting that reads revert when a misaligned memory address is provided + function test_readMem_readInvalidAddress_reverts() external { + uint64 addr = 0x100; + uint64 word = 0x11_22_33_44_55_66_77_88; + bytes32 root; + bytes memory proof; + (root, proof) = ffi.getCannonMemory64Proof(addr, word); + vm.expectRevert(InvalidAddress.selector); + mem.readMem(root, addr + 4, 0, proof); + } + + /// @dev Static unit test asserting that reads revert when an invalid proof is provided + function test_readMem_readInvalidProof_reverts() external { + uint64 addr = 0x100; + uint64 word = 0x11_22_33_44_55_66_77_88; + bytes32 root; + bytes memory proof; + (root, proof) = ffi.getCannonMemory64Proof(addr, word); + vm.assertTrue(proof[64] != 0x0); // make sure the proof is tampered + proof[64] = 0x00; + vm.expectRevert(InvalidMemoryProof.selector); + mem.readMem(root, addr, 0, proof); + } + + /// @dev Static unit test asserting that reads from a non-zero proof index succeeds + function test_readMemNonZeroProofIndex_succeeds() external { + uint64 addr = 0x100; + uint64 word = 0x11_22_33_44_55_66_77_88; + uint64 addr2 = 0xFF_FF_FF_FF_00_00_00_88; + uint64 word2 = 0xF1_F2_F3_F4_F5_F6_F7_F8; + bytes32 root; + bytes memory proof; + (root, proof) = ffi.getCannonMemory64Proof(addr, word, addr2, word2); + + uint64 readWord = mem.readMem(root, addr, 0, proof); + assertEq(readWord, word); + + readWord = mem.readMem(root, addr2, 1, proof); + assertEq(readWord, word2); + } + + /// @dev Static unit test asserting basic memory write functionality + function test_writeMem_succeeds() external { + uint64 addr = 0x100; + bytes memory zeroProof; + (, zeroProof) = ffi.getCannonMemory64Proof(addr, 0); + + uint64 word = 0x11_22_33_44_55_66_77_88; + (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word); + + bytes32 newRoot = mem.writeMem(addr, word, 0, zeroProof); + assertEq(newRoot, expectedRoot); + } + + // @dev Static unit test asserting that writes to high memory succeeds + function test_writeMemHighMem_succeeds() external { + uint64 addr = 0xFF_FF_FF_FF_00_00_00_88; + bytes memory zeroProof; + (, zeroProof) = ffi.getCannonMemory64Proof(addr, 0); + + uint64 word = 0x11_22_33_44_55_66_77_88; + (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word); + + bytes32 newRoot = mem.writeMem(addr, word, 0, zeroProof); + assertEq(newRoot, expectedRoot); + } + + /// @dev Static unit test asserting that non-zero memory word is overwritten + function test_writeMemNonZeroProofOffset_succeeds() external { + uint64 addr = 0x100; + uint64 word = 0x11_22_33_44_55_66_77_88; + uint64 addr2 = 0x108; + uint64 word2 = 0x55_55_55_55_77_77_77_77; + bytes memory initProof; + (, initProof) = ffi.getCannonMemory64Proof(addr, word, addr2, word2); + + uint64 word3 = 0x44_44_44_44_44_44_44_44; + (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word, addr2, word2, addr2, word3); + + bytes32 newRoot = mem.writeMem(addr2, word3, 1, initProof); + assertEq(newRoot, expectedRoot); + } + + /// @dev Static unit test asserting that a zerod memory word is set for a non-zero memory proof + function test_writeMemUniqueAccess_succeeds() external { + uint64 addr = 0x100; + uint64 word = 0x11_22_33_44_55_66_77_88; + uint64 addr2 = 0x108; + uint64 word2 = 0x55_55_55_55_77_77_77_77; + bytes memory initProof; + (, initProof) = ffi.getCannonMemory64Proof(addr, word, addr2, word2); + + uint64 addr3 = 0xAA_AA_AA_AA_00; + uint64 word3 = 0x44_44_44_44_44_44_44_44; + (, bytes memory addr3Proof) = ffi.getCannonMemory64Proof2(addr, word, addr2, word2, addr3); + (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word, addr2, word2, addr3, word3); + + bytes32 newRoot = mem.writeMem(addr3, word3, 0, addr3Proof); + assertEq(newRoot, expectedRoot); + + newRoot = mem.writeMem(addr3 + 8, word3, 0, addr3Proof); + assertNotEq(newRoot, expectedRoot); + + newRoot = mem.writeMem(addr3, word3 + 1, 0, addr3Proof); + assertNotEq(newRoot, expectedRoot); + } + + /// @dev Static unit test asserting that writes succeeds in overwriting a non-zero memory word + function test_writeMemNonZeroMem_succeeds() external { + uint64 addr = 0x100; + uint64 word = 0x11_22_33_44_55_66_77_88; + bytes memory initProof; + (, initProof) = ffi.getCannonMemory64Proof(addr, word); + + uint64 word2 = 0x55_55_55_55_77_77_77_77; + (bytes32 expectedRoot,) = ffi.getCannonMemory64Proof(addr, word, addr + 8, word2); + + bytes32 newRoot = mem.writeMem(addr + 8, word2, 0, initProof); + assertEq(newRoot, expectedRoot); + } + + /// @dev Static unit test asserting that writes revert when a misaligned memory address is provided + function test_writeMem_writeMemInvalidAddress_reverts() external { + bytes memory zeroProof; + (, zeroProof) = ffi.getCannonMemory64Proof(0x100, 0); + vm.expectRevert(InvalidAddress.selector); + mem.writeMem(0x104, 0x0, 0, zeroProof); + } +} + +contract MIPS64MemoryWithCalldata { + function readMem( + bytes32 _root, + uint64 _addr, + uint8 _proofIndex, + bytes calldata /* _proof */ + ) + external + pure + returns (uint64 out_) + { + uint256 proofDataOffset = 4 + 32 + 32 + 32 + 32 + 32; + uint256 proofOffset = MIPS64Memory.memoryProofOffset(proofDataOffset, _proofIndex); + return MIPS64Memory.readMem(_root, _addr, proofOffset); + } + + function writeMem( + uint64 _addr, + uint64 _value, + uint8 _proofIndex, + bytes calldata /* _proof */ + ) + external + pure + returns (bytes32 root_) + { + uint256 proofDataOffset = 4 + 32 + 32 + 32 + 32 + 32; + uint256 proofOffset = MIPS64Memory.memoryProofOffset(proofDataOffset, _proofIndex); + return MIPS64Memory.writeMem(_addr, proofOffset, _value); + } +} diff --git a/packages/contracts-bedrock/test/cannon/PreimageOracle.t.sol b/packages/contracts-bedrock/test/cannon/PreimageOracle.t.sol index 1d01f699632ac..d6c7c520a5f1a 100644 --- a/packages/contracts-bedrock/test/cannon/PreimageOracle.t.sol +++ b/packages/contracts-bedrock/test/cannon/PreimageOracle.t.sol @@ -1,22 +1,34 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Testing import { Test, Vm, console2 as console } from "forge-std/Test.sol"; -import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; -import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; +// Scripts +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Libraries import { LibKeccak } from "@lib-keccak/LibKeccak.sol"; +import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; import { Bytes } from "src/libraries/Bytes.sol"; import { Process } from "scripts/libraries/Process.sol"; import "src/cannon/libraries/CannonErrors.sol"; import "src/cannon/libraries/CannonTypes.sol"; +// Interfaces +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; + contract PreimageOracle_Test is Test { - PreimageOracle oracle; + IPreimageOracle oracle; /// @notice Sets up the testing suite. function setUp() public { - oracle = new PreimageOracle(0, 0); + oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ); vm.label(address(oracle), "PreimageOracle"); } @@ -25,7 +37,10 @@ contract PreimageOracle_Test is Test { function testFuzz_constructor_challengePeriodTooLarge_reverts(uint256 _challengePeriod) public { _challengePeriod = bound(_challengePeriod, uint256(type(uint64).max) + 1, type(uint256).max); vm.expectRevert("PreimageOracle: challenge period too large"); - new PreimageOracle(0, _challengePeriod); + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, _challengePeriod))) + }); } /// @notice Test the pre-image key computation with a known pre-image. @@ -306,11 +321,18 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { uint256 internal constant CHALLENGE_PERIOD = 1 days; uint256 internal constant TEST_UUID = 0xFACADE; - PreimageOracle internal oracle; + IPreimageOracle internal oracle; /// @notice Sets up the testing suite. function setUp() public { - oracle = new PreimageOracle({ _minProposalSize: MIN_SIZE_BYTES, _challengePeriod: CHALLENGE_PERIOD }); + oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IPreimageOracle.__constructor__, (MIN_SIZE_BYTES, CHALLENGE_PERIOD)) + ) + }) + ); vm.label(address(oracle), "PreimageOracle"); // Set `tx.origin` and `msg.sender` to `address(this)` so that it may behave like an EOA for `addLeavesLPP`. @@ -336,7 +358,14 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { /// @notice Tests that the `initLPP` function reverts when the part offset is out of bounds of the full preimage. function test_initLPP_sizeTooSmall_reverts() public { - oracle = new PreimageOracle({ _minProposalSize: 1000, _challengePeriod: CHALLENGE_PERIOD }); + oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IPreimageOracle.__constructor__, (1000, CHALLENGE_PERIOD)) + ) + }) + ); // Allocate the preimage data. bytes memory data = new bytes(136); @@ -362,7 +391,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { } /// @notice Gas snapshot for `addLeaves` - function test_addLeaves_gasSnapshot() public { + function test_addLeaves_gasSnapshot_benchmark() public { // Allocate the preimage data. bytes memory data = new bytes(136 * 500); for (uint256 i; i < data.length; i++) { @@ -552,7 +581,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Create a proof array with 16 elements. bytes32[] memory preProof = new bytes32[](16); @@ -607,7 +636,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); leaves[0].stateCommitment = stateCommitments[0]; leaves[1].stateCommitment = stateCommitments[1]; @@ -676,7 +705,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Create a proof array with 16 elements. bytes32[] memory preProof = new bytes32[](16); @@ -723,7 +752,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Finalize the proposal. vm.expectRevert(ActiveProposal.selector); @@ -751,7 +780,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Finalize the proposal. vm.expectRevert(ActiveProposal.selector); @@ -784,7 +813,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Create a proof array with 16 elements. bytes32[] memory preProof = new bytes32[](16); @@ -830,7 +859,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Create a proof array with 16 elements. bytes32[] memory preProof = new bytes32[](16); @@ -860,7 +889,6 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { /// @notice Tests that squeezing a large preimage proposal after the challenge period has passed always succeeds and /// persists the correct data. - /// forge-config: ciheavy.fuzz.runs = 512 function testFuzz_squeezeLPP_succeeds(uint256 _numBlocks, uint32 _partOffset) public { _numBlocks = bound(_numBlocks, 1, 2 ** 8); _partOffset = uint32(bound(_partOffset, 0, _numBlocks * LibKeccak.BLOCK_SIZE_BYTES + 8 - 1)); @@ -883,7 +911,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrixB; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); // Fetch the merkle proofs for the pre/post state leaves in the proposal tree. bytes32 canonicalRoot = oracle.getTreeRootLPP(address(this), TEST_UUID); @@ -950,7 +978,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Create a proof array with 16 elements. bytes32[] memory p = new bytes32[](16); @@ -990,7 +1018,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); leaves[0].stateCommitment = stateCommitments[0]; leaves[1].stateCommitment = stateCommitments[1]; @@ -1030,7 +1058,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); leaves[0].stateCommitment = stateCommitments[0]; leaves[1].stateCommitment = stateCommitments[1]; @@ -1058,7 +1086,6 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { /// @notice Tests that challenging the first divergence in a large preimage proposal at an arbitrary location /// in the leaf values always succeeds. - /// forge-config: ciheavy.fuzz.runs = 512 function testFuzz_challenge_arbitraryLocation_succeeds(uint256 _lastCorrectLeafIdx, uint256 _numBlocks) public { _numBlocks = bound(_numBlocks, 1, 2 ** 8); _lastCorrectLeafIdx = bound(_lastCorrectLeafIdx, 0, _numBlocks - 1); @@ -1079,7 +1106,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added and corrupt the state commitments. LibKeccak.StateMatrix memory matrixB; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); for (uint256 i = _lastCorrectLeafIdx + 1; i < leaves.length; i++) { leaves[i].stateCommitment = 0; } @@ -1111,7 +1138,6 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { } /// @notice Tests that challenging the a divergence in a large preimage proposal at the first leaf always succeeds. - /// forge-config: ciheavy.fuzz.runs = 1024 function testFuzz_challengeFirst_succeeds(uint256 _numBlocks) public { _numBlocks = bound(_numBlocks, 1, 2 ** 8); @@ -1130,7 +1156,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added and corrupt the state commitments. LibKeccak.StateMatrix memory matrixB; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrixB, data); for (uint256 i = 0; i < leaves.length; i++) { leaves[i].stateCommitment = 0; } @@ -1169,7 +1195,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data); // Create a proof array with 16 elements. bytes32[] memory preProof = new bytes32[](16); @@ -1221,7 +1247,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); leaves[0].stateCommitment = stateCommitments[0]; leaves[1].stateCommitment = stateCommitments[1]; leaves[2].stateCommitment = stateCommitments[2]; @@ -1275,7 +1301,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); leaves[0].stateCommitment = stateCommitments[0]; leaves[1].stateCommitment = stateCommitments[1]; leaves[2].stateCommitment = stateCommitments[2]; @@ -1329,7 +1355,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { // Construct the leaf preimage data for the blocks added. LibKeccak.StateMatrix memory matrix; - PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); + IPreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, phonyData); leaves[0].stateCommitment = stateCommitments[0]; leaves[1].stateCommitment = stateCommitments[1]; leaves[2].stateCommitment = stateCommitments[2]; @@ -1366,7 +1392,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { } /// @notice Hashes leaf data for the preimage proposals tree - function _hashLeaf(PreimageOracle.Leaf memory _leaf) internal pure returns (bytes32 leaf_) { + function _hashLeaf(IPreimageOracle.Leaf memory _leaf) internal pure returns (bytes32 leaf_) { leaf_ = keccak256(abi.encodePacked(_leaf.input, _leaf.index, _leaf.stateCommitment)); } @@ -1377,18 +1403,18 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { ) internal pure - returns (PreimageOracle.Leaf[] memory leaves_) + returns (IPreimageOracle.Leaf[] memory leaves_) { bytes memory data = LibKeccak.padMemory(_data); uint256 numCommitments = data.length / LibKeccak.BLOCK_SIZE_BYTES; - leaves_ = new PreimageOracle.Leaf[](numCommitments); + leaves_ = new IPreimageOracle.Leaf[](numCommitments); for (uint256 i = 0; i < numCommitments; i++) { bytes memory blockSlice = Bytes.slice(data, i * LibKeccak.BLOCK_SIZE_BYTES, LibKeccak.BLOCK_SIZE_BYTES); LibKeccak.absorb(_stateMatrix, blockSlice); LibKeccak.permutation(_stateMatrix); - leaves_[i] = PreimageOracle.Leaf({ + leaves_[i] = IPreimageOracle.Leaf({ input: blockSlice, index: uint32(i), stateCommitment: keccak256(abi.encode(_stateMatrix)) @@ -1440,7 +1466,7 @@ contract PreimageOracle_LargePreimageProposals_Test is Test { /// constructed with `_leaves`. function _generateProof( uint256 _leafIdx, - PreimageOracle.Leaf[] memory _leaves + IPreimageOracle.Leaf[] memory _leaves ) internal returns (bytes32 root_, bytes32[] memory proof_) diff --git a/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol b/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol index b5dbac76f6c6a..524423292058f 100644 --- a/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol +++ b/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol @@ -49,9 +49,7 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In assert(l2BlockNumber < gameProxy.l2BlockNumber()); // Mock the state that we want. - vm.mockCall( - address(gameProxy), abi.encodeWithSelector(gameProxy.status.selector), abi.encode(GameStatus.DEFENDER_WINS) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -66,15 +64,13 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In /// @dev Tests that updating the anchor state fails when the game state is valid but older. function test_tryUpdateAnchorState_validOlderState_fails() public { // Confirm that the anchor state is newer than the game state. - vm.mockCall(address(gameProxy), abi.encodeWithSelector(gameProxy.l2BlockNumber.selector), abi.encode(0)); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(0)); (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); assert(l2BlockNumber >= gameProxy.l2BlockNumber()); // Mock the state that we want. - vm.mockCall(address(gameProxy), abi.encodeWithSelector(gameProxy.l2BlockNumber.selector), abi.encode(0)); - vm.mockCall( - address(gameProxy), abi.encodeWithSelector(gameProxy.status.selector), abi.encode(GameStatus.DEFENDER_WINS) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(0)); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -93,11 +89,7 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In assert(l2BlockNumber < gameProxy.l2BlockNumber()); // Mock the state that we want. - vm.mockCall( - address(gameProxy), - abi.encodeWithSelector(gameProxy.status.selector), - abi.encode(GameStatus.CHALLENGER_WINS) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -118,8 +110,8 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In // Mock the state that we want. vm.mockCall( address(disputeGameFactory), - abi.encodeWithSelector( - disputeGameFactory.games.selector, gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData() + abi.encodeCall( + disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData()) ), abi.encode(address(0), 0) ); @@ -138,13 +130,16 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In function test_setAnchorState_invalidGame_fails() public { // Confirm that the anchor state is older than the game state. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - require(l2BlockNumber < gameProxy.l2BlockNumber(), "l2BlockNumber < gameProxy.l2BlockNumber()"); + require( + l2BlockNumber < gameProxy.l2BlockNumber(), + "AnchorStateRegistry_TryUpdateAnchorState_Test: l2BlockNumber < gameProxy.l2BlockNumber()" + ); // Mock the state that we want. vm.mockCall( address(disputeGameFactory), - abi.encodeWithSelector( - disputeGameFactory.games.selector, gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData() + abi.encodeCall( + disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData()) ), abi.encode(address(0), 0) ); @@ -165,11 +160,7 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); // Mock the state that we want. - vm.mockCall( - address(gameProxy), - abi.encodeWithSelector(gameProxy.status.selector), - abi.encode(GameStatus.CHALLENGER_WINS) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); // Set the anchor state. vm.prank(superchainConfig.guardian()); @@ -187,9 +178,7 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); // Mock the state that we want. - vm.mockCall( - address(gameProxy), abi.encodeWithSelector(gameProxy.status.selector), abi.encode(GameStatus.IN_PROGRESS) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS)); // Set the anchor state. vm.prank(superchainConfig.guardian()); @@ -205,9 +194,7 @@ contract AnchorStateRegistry_TryUpdateAnchorState_Test is AnchorStateRegistry_In /// @dev Tests that setting the anchor state succeeds. function test_setAnchorState_succeeds() public { // Mock the state that we want. - vm.mockCall( - address(gameProxy), abi.encodeWithSelector(gameProxy.status.selector), abi.encode(GameStatus.DEFENDER_WINS) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); // Set the anchor state. vm.prank(superchainConfig.guardian()); diff --git a/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol b/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol index 8982eae96bf60..1cbaf0c1eb258 100644 --- a/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol +++ b/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol @@ -18,7 +18,6 @@ contract DelayedWETH_Init is CommonTest { event Unwrap(address indexed src, uint256 wad); function setUp() public virtual override { - super.enableFaultProofs(); super.setUp(); // Transfer ownership of delayed WETH to the test contract. @@ -65,6 +64,110 @@ contract DelayedWETH_Unlock_Test is DelayedWETH_Init { } contract DelayedWETH_Withdraw_Test is DelayedWETH_Init { + /// @dev Tests that withdrawing while unlocked and delay has passed is successful. + function test_withdraw_whileUnlocked_succeeds() public { + // Deposit some WETH. + vm.prank(alice); + delayedWeth.deposit{ value: 1 ether }(); + uint256 balance = address(alice).balance; + + // Unlock the withdrawal. + vm.prank(alice); + delayedWeth.unlock(alice, 1 ether); + + // Wait for the delay. + vm.warp(block.timestamp + delayedWeth.delay() + 1); + + // Withdraw the WETH. + vm.expectEmit(true, true, false, false); + emit Withdrawal(address(alice), 1 ether); + vm.prank(alice); + delayedWeth.withdraw(1 ether); + assertEq(address(alice).balance, balance + 1 ether); + } + + /// @dev Tests that withdrawing when unlock was not called fails. + function test_withdraw_whileLocked_fails() public { + // Deposit some WETH. + vm.prank(alice); + delayedWeth.deposit{ value: 1 ether }(); + uint256 balance = address(alice).balance; + + // Withdraw fails when unlock not called. + vm.expectRevert("DelayedWETH: withdrawal not unlocked"); + vm.prank(alice); + delayedWeth.withdraw(0 ether); + assertEq(address(alice).balance, balance); + } + + /// @dev Tests that withdrawing while locked and delay has not passed fails. + function test_withdraw_whileLockedNotLongEnough_fails() public { + // Deposit some WETH. + vm.prank(alice); + delayedWeth.deposit{ value: 1 ether }(); + uint256 balance = address(alice).balance; + + // Call unlock. + vm.prank(alice); + delayedWeth.unlock(alice, 1 ether); + + // Wait for the delay, but not long enough. + vm.warp(block.timestamp + delayedWeth.delay() - 1); + + // Withdraw fails when delay not met. + vm.expectRevert("DelayedWETH: withdrawal delay not met"); + vm.prank(alice); + delayedWeth.withdraw(1 ether); + assertEq(address(alice).balance, balance); + } + + /// @dev Tests that withdrawing more than unlocked amount fails. + function test_withdraw_tooMuch_fails() public { + // Deposit some WETH. + vm.prank(alice); + delayedWeth.deposit{ value: 1 ether }(); + uint256 balance = address(alice).balance; + + // Unlock the withdrawal. + vm.prank(alice); + delayedWeth.unlock(alice, 1 ether); + + // Wait for the delay. + vm.warp(block.timestamp + delayedWeth.delay() + 1); + + // Withdraw too much fails. + vm.expectRevert("DelayedWETH: insufficient unlocked withdrawal"); + vm.prank(alice); + delayedWeth.withdraw(2 ether); + assertEq(address(alice).balance, balance); + } + + /// @dev Tests that withdrawing while paused fails. + function test_withdraw_whenPaused_fails() public { + // Deposit some WETH. + vm.prank(alice); + delayedWeth.deposit{ value: 1 ether }(); + + // Unlock the withdrawal. + vm.prank(alice); + delayedWeth.unlock(alice, 1 ether); + + // Wait for the delay. + vm.warp(block.timestamp + delayedWeth.delay() + 1); + + // Pause the contract. + address guardian = optimismPortal.guardian(); + vm.prank(guardian); + superchainConfig.pause("identifier"); + + // Withdraw fails. + vm.expectRevert("DelayedWETH: contract is paused"); + vm.prank(alice); + delayedWeth.withdraw(1 ether); + } +} + +contract DelayedWETH_WithdrawFrom_Test is DelayedWETH_Init { /// @dev Tests that withdrawing while unlocked and delay has passed is successful. function test_withdraw_whileUnlocked_succeeds() public { // Deposit some WETH. diff --git a/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol b/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol index 9619832135e5c..f40a641994b0f 100644 --- a/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol +++ b/packages/contracts-bedrock/test/dispute/DisputeGameFactory.t.sol @@ -13,8 +13,8 @@ import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; // Interfaces -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; contract DisputeGameFactory_Init is CommonTest { FakeClone fakeClone; @@ -24,7 +24,6 @@ contract DisputeGameFactory_Init is CommonTest { event InitBondUpdated(GameType indexed gameType, uint256 indexed newBond); function setUp() public virtual override { - super.enableFaultProofs(); super.setUp(); fakeClone = new FakeClone(); diff --git a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol index 8cfb602e3d313..50fde1fad4cd1 100644 --- a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol @@ -6,10 +6,12 @@ import { Test } from "forge-std/Test.sol"; import { Vm } from "forge-std/Vm.sol"; import { DisputeGameFactory_Init } from "test/dispute/DisputeGameFactory.t.sol"; import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; +import { stdError } from "forge-std/StdError.sol"; + +// Scripts +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Contracts -import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; -import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol"; import { DisputeActor, HonestDisputeActor } from "test/actors/FaultDisputeActors.sol"; // Libraries @@ -22,11 +24,11 @@ import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; // Interfaces -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IPreimageOracle } from "interfaces/dispute/IBigStepper.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; contract FaultDisputeGame_Init is DisputeGameFactory_Init { /// @dev The type of the game being tested. @@ -53,25 +55,40 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init { // Set preimage oracle challenge period to something arbitrary (4 seconds) just so we can // actually test the clock extensions later on. This is not a realistic value. - PreimageOracle oracle = new PreimageOracle(0, 4); - AlphabetVM _vm = new AlphabetVM(absolutePrestate, IPreimageOracle(address(oracle))); + AlphabetVM _vm = new AlphabetVM( + absolutePrestate, + IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 4))) + }) + ) + ); // Deploy an implementation of the fault game gameImpl = IFaultDisputeGame( - address( - new FaultDisputeGame({ - _gameType: GAME_TYPE, - _absolutePrestate: absolutePrestate, - _maxGameDepth: 2 ** 3, - _splitDepth: 2 ** 2, - _clockExtension: Duration.wrap(3 hours), - _maxClockDuration: Duration.wrap(3.5 days), - _vm: _vm, - _weth: delayedWeth, - _anchorStateRegistry: anchorStateRegistry, - _l2ChainId: 10 - }) - ) + DeployUtils.create1({ + _name: "FaultDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGame.__constructor__, + ( + IFaultDisputeGame.GameConstructorParams({ + gameType: GAME_TYPE, + absolutePrestate: absolutePrestate, + maxGameDepth: 2 ** 3, + splitDepth: 2 ** 2, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days), + vm: _vm, + weth: delayedWeth, + anchorStateRegistry: anchorStateRegistry, + l2ChainId: 10 + }) + ) + ) + ) + }) ); // Register the game implementation with the factory. @@ -123,21 +140,37 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { /// @dev Tests that the constructor of the `FaultDisputeGame` reverts when the `MAX_GAME_DEPTH` parameter is /// greater than `LibPosition.MAX_POSITION_BITLEN - 1`. function testFuzz_constructor_maxDepthTooLarge_reverts(uint256 _maxGameDepth) public { - AlphabetVM alphabetVM = new AlphabetVM(absolutePrestate, IPreimageOracle(address(new PreimageOracle(0, 0)))); + IPreimageOracle oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ); + AlphabetVM alphabetVM = new AlphabetVM(absolutePrestate, oracle); _maxGameDepth = bound(_maxGameDepth, LibPosition.MAX_POSITION_BITLEN, type(uint256).max - 1); vm.expectRevert(MaxDepthTooLarge.selector); - new FaultDisputeGame({ - _gameType: GAME_TYPE, - _absolutePrestate: absolutePrestate, - _maxGameDepth: _maxGameDepth, - _splitDepth: _maxGameDepth + 1, - _clockExtension: Duration.wrap(3 hours), - _maxClockDuration: Duration.wrap(3.5 days), - _vm: alphabetVM, - _weth: IDelayedWETH(payable(address(0))), - _anchorStateRegistry: IAnchorStateRegistry(address(0)), - _l2ChainId: 10 + DeployUtils.create1({ + _name: "FaultDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGame.__constructor__, + ( + IFaultDisputeGame.GameConstructorParams({ + gameType: GAME_TYPE, + absolutePrestate: absolutePrestate, + maxGameDepth: _maxGameDepth, + splitDepth: _maxGameDepth + 1, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days), + vm: alphabetVM, + weth: IDelayedWETH(payable(address(0))), + anchorStateRegistry: IAnchorStateRegistry(address(0)), + l2ChainId: 10 + }) + ) + ) + ) }); } @@ -147,72 +180,121 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { function testFuzz_constructor_oracleChallengePeriodTooLarge_reverts(uint256 _challengePeriod) public { _challengePeriod = bound(_challengePeriod, uint256(type(uint64).max) + 1, type(uint256).max); - PreimageOracle oracle = new PreimageOracle(0, 0); + IPreimageOracle oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ); AlphabetVM alphabetVM = new AlphabetVM(absolutePrestate, IPreimageOracle(address(oracle))); // PreimageOracle constructor will revert if the challenge period is too large, so we need // to mock the call to pretend this is a bugged implementation where the challenge period // is allowed to be too large. - vm.mockCall( - address(oracle), abi.encodeWithSelector(oracle.challengePeriod.selector), abi.encode(_challengePeriod) - ); + vm.mockCall(address(oracle), abi.encodeCall(IPreimageOracle.challengePeriod, ()), abi.encode(_challengePeriod)); vm.expectRevert(InvalidChallengePeriod.selector); - new FaultDisputeGame({ - _gameType: GAME_TYPE, - _absolutePrestate: absolutePrestate, - _maxGameDepth: 2 ** 3, - _splitDepth: 2 ** 2, - _clockExtension: Duration.wrap(3 hours), - _maxClockDuration: Duration.wrap(3.5 days), - _vm: alphabetVM, - _weth: IDelayedWETH(payable(address(0))), - _anchorStateRegistry: IAnchorStateRegistry(address(0)), - _l2ChainId: 10 + DeployUtils.create1({ + _name: "FaultDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGame.__constructor__, + ( + IFaultDisputeGame.GameConstructorParams({ + gameType: GAME_TYPE, + absolutePrestate: absolutePrestate, + maxGameDepth: 2 ** 3, + splitDepth: 2 ** 2, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days), + vm: alphabetVM, + weth: IDelayedWETH(payable(address(0))), + anchorStateRegistry: IAnchorStateRegistry(address(0)), + l2ChainId: 10 + }) + ) + ) + ) }); } /// @dev Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` /// parameter is greater than or equal to the `MAX_GAME_DEPTH` function testFuzz_constructor_invalidSplitDepth_reverts(uint256 _splitDepth) public { - AlphabetVM alphabetVM = new AlphabetVM(absolutePrestate, IPreimageOracle(address(new PreimageOracle(0, 0)))); + AlphabetVM alphabetVM = new AlphabetVM( + absolutePrestate, + IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ) + ); uint256 maxGameDepth = 2 ** 3; _splitDepth = bound(_splitDepth, maxGameDepth - 1, type(uint256).max); vm.expectRevert(InvalidSplitDepth.selector); - new FaultDisputeGame({ - _gameType: GAME_TYPE, - _absolutePrestate: absolutePrestate, - _maxGameDepth: maxGameDepth, - _splitDepth: _splitDepth, - _clockExtension: Duration.wrap(3 hours), - _maxClockDuration: Duration.wrap(3.5 days), - _vm: alphabetVM, - _weth: IDelayedWETH(payable(address(0))), - _anchorStateRegistry: IAnchorStateRegistry(address(0)), - _l2ChainId: 10 + DeployUtils.create1({ + _name: "FaultDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGame.__constructor__, + ( + IFaultDisputeGame.GameConstructorParams({ + gameType: GAME_TYPE, + absolutePrestate: absolutePrestate, + maxGameDepth: maxGameDepth, + splitDepth: _splitDepth, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days), + vm: alphabetVM, + weth: IDelayedWETH(payable(address(0))), + anchorStateRegistry: IAnchorStateRegistry(address(0)), + l2ChainId: 10 + }) + ) + ) + ) }); } /// @dev Tests that the constructor of the `FaultDisputeGame` reverts when the `_splitDepth` /// parameter is less than the minimum split depth (currently 2). function testFuzz_constructor_lowSplitDepth_reverts(uint256 _splitDepth) public { - AlphabetVM alphabetVM = new AlphabetVM(absolutePrestate, IPreimageOracle(address(new PreimageOracle(0, 0)))); + AlphabetVM alphabetVM = new AlphabetVM( + absolutePrestate, + IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ) + ); uint256 minSplitDepth = 2; _splitDepth = bound(_splitDepth, 0, minSplitDepth - 1); vm.expectRevert(InvalidSplitDepth.selector); - new FaultDisputeGame({ - _gameType: GAME_TYPE, - _absolutePrestate: absolutePrestate, - _maxGameDepth: 2 ** 3, - _splitDepth: _splitDepth, - _clockExtension: Duration.wrap(3 hours), - _maxClockDuration: Duration.wrap(3.5 days), - _vm: alphabetVM, - _weth: IDelayedWETH(payable(address(0))), - _anchorStateRegistry: IAnchorStateRegistry(address(0)), - _l2ChainId: 10 + DeployUtils.create1({ + _name: "FaultDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGame.__constructor__, + ( + IFaultDisputeGame.GameConstructorParams({ + gameType: GAME_TYPE, + absolutePrestate: absolutePrestate, + maxGameDepth: 2 ** 3, + splitDepth: _splitDepth, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days), + vm: alphabetVM, + weth: IDelayedWETH(payable(address(0))), + anchorStateRegistry: IAnchorStateRegistry(address(0)), + l2ChainId: 10 + }) + ) + ) + ) }); } @@ -224,7 +306,15 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ) public { - AlphabetVM alphabetVM = new AlphabetVM(absolutePrestate, IPreimageOracle(address(new PreimageOracle(0, 0)))); + AlphabetVM alphabetVM = new AlphabetVM( + absolutePrestate, + IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ) + ); // Force the clock extension * 2 to be greater than the max clock duration, but keep things within // bounds of the uint64 type. @@ -232,17 +322,27 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { _clockExtension = uint64(bound(_clockExtension, _maxClockDuration / 2 + 1, type(uint64).max / 2)); vm.expectRevert(InvalidClockExtension.selector); - new FaultDisputeGame({ - _gameType: GAME_TYPE, - _absolutePrestate: absolutePrestate, - _maxGameDepth: 16, - _splitDepth: 8, - _clockExtension: Duration.wrap(_clockExtension), - _maxClockDuration: Duration.wrap(_maxClockDuration), - _vm: alphabetVM, - _weth: IDelayedWETH(payable(address(0))), - _anchorStateRegistry: IAnchorStateRegistry(address(0)), - _l2ChainId: 10 + DeployUtils.create1({ + _name: "FaultDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IFaultDisputeGame.__constructor__, + ( + IFaultDisputeGame.GameConstructorParams({ + gameType: GAME_TYPE, + absolutePrestate: absolutePrestate, + maxGameDepth: 16, + splitDepth: 8, + clockExtension: Duration.wrap(_clockExtension), + maxClockDuration: Duration.wrap(_maxClockDuration), + vm: alphabetVM, + weth: IDelayedWETH(payable(address(0))), + anchorStateRegistry: IAnchorStateRegistry(address(0)), + l2ChainId: 10 + }) + ) + ) + ) }); } @@ -361,6 +461,17 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { gameProxy.initialize(); } + /// @dev Tests that startingOutputRoot and it's getters are set correctly. + function test_startingOutputRootGetters_succeeds() public view { + (Hash root, uint256 l2BlockNumber) = gameProxy.startingOutputRoot(); + (Hash anchorRoot, uint256 anchorRootBlockNumber) = anchorStateRegistry.anchors(GAME_TYPE); + + assertEq(gameProxy.startingBlockNumber(), l2BlockNumber); + assertEq(gameProxy.startingBlockNumber(), anchorRootBlockNumber); + assertEq(Hash.unwrap(gameProxy.startingRootHash()), Hash.unwrap(root)); + assertEq(Hash.unwrap(gameProxy.startingRootHash()), Hash.unwrap(anchorRoot)); + } + /// @dev Tests that the user cannot control the first 4 bytes of the CWIA data, disallowing them to control the /// entrypoint when no calldata is provided to a call. function test_cwiaCalldata_userCannotControlSelector_succeeds() public { @@ -447,11 +558,11 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { Claim claim = _dummyClaim(); // Expect an out of bounds revert for an attack - vm.expectRevert(abi.encodeWithSignature("Panic(uint256)", 0x32)); + vm.expectRevert(stdError.indexOOBError); gameProxy.attack(_dummyClaim(), 1, claim); // Expect an out of bounds revert for a defense - vm.expectRevert(abi.encodeWithSignature("Panic(uint256)", 0x32)); + vm.expectRevert(stdError.indexOOBError); gameProxy.defend(_dummyClaim(), 1, claim); } @@ -792,7 +903,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber + 1)); // Challenge the L2 block number. - FaultDisputeGame fdg = FaultDisputeGame(address(game)); + IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); fdg.challengeRootL2Block(outputRootProof, headerRLP); // Ensure that a duplicate challenge reverts. @@ -831,7 +942,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { IDisputeGame game = disputeGameFactory.create{ value: 0.1 ether }( GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber + 1) ); - FaultDisputeGame fdg = FaultDisputeGame(address(game)); + IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); // Attack the root as 0xb0b uint256 bond = _getRequiredBond(0); @@ -888,7 +999,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber)); // Challenge the L2 block number. - FaultDisputeGame fdg = FaultDisputeGame(address(game)); + IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); vm.expectRevert(BlockNumberMatches.selector); fdg.challengeRootL2Block(outputRootProof, headerRLP); @@ -918,7 +1029,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { // Create the dispute game with the output root at the wrong L2 block number. IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(1)); - FaultDisputeGame fdg = FaultDisputeGame(address(game)); + IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); vm.expectRevert(InvalidHeaderRLP.selector); fdg.challengeRootL2Block(outputRootProof, hex""); @@ -931,7 +1042,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { // Create the dispute game with the output root at the wrong L2 block number. IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(1)); - FaultDisputeGame fdg = FaultDisputeGame(address(game)); + IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); vm.expectRevert(InvalidHeaderRLP.selector); fdg.challengeRootL2Block(outputRootProof, hex""); @@ -1615,14 +1726,14 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { /// resolves in favor of the defender but the game state is not newer than the anchor state. function test_resolve_validOlderStateSameAnchor_succeeds() public { // Mock the game block to be older than the game state. - vm.mockCall(address(gameProxy), abi.encodeWithSelector(gameProxy.l2BlockNumber.selector), abi.encode(0)); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(0)); // Confirm that the anchor state is newer than the game state. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); assert(l2BlockNumber >= gameProxy.l2BlockNumber()); // Resolve the game. - vm.mockCall(address(gameProxy), abi.encodeWithSelector(gameProxy.l2BlockNumber.selector), abi.encode(0)); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2BlockNumber, ()), abi.encode(0)); vm.warp(block.timestamp + 3 days + 12 hours); gameProxy.resolveClaim(0, 0); assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS)); @@ -1839,7 +1950,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { function test_addLocalData_l2BlockNumberExtension_succeeds() public { // Deploy a new dispute game with a L2 block number claim of 8. This is directly in the middle of // the leaves in our output bisection test tree, at SPLIT_DEPTH = 2 ** 2 - FaultDisputeGame game = FaultDisputeGame( + IFaultDisputeGame game = IFaultDisputeGame( address(disputeGameFactory.create(GAME_TYPE, Claim.wrap(bytes32(uint256(0xFF))), abi.encode(uint256(8)))) ); @@ -1898,6 +2009,18 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { assertEq(datLen, expectedLen); } + /// @dev Tests that if the game is not in progress, querying of `getChallengerDuration` reverts + function test_getChallengerDuration_gameNotInProgress_reverts() public { + // resolve the game + vm.warp(block.timestamp + gameProxy.maxClockDuration().raw()); + + gameProxy.resolveClaim(0, 0); + gameProxy.resolve(); + + vm.expectRevert(GameNotInProgress.selector); + gameProxy.getChallengerDuration(1); + } + /// @dev Static unit test asserting that resolveClaim isn't possible if there's time /// left for a counter. function test_resolution_lastSecondDisputes_succeeds() public { @@ -2552,7 +2675,7 @@ contract FaultDispute_1v1_Actors_Test is FaultDisputeGame_Init { (uint256 numMovesA,) = dishonest.move(); (uint256 numMovesB, bool success) = honest.move(); - require(success, "Honest actor's moves should always be successful"); + require(success, "FaultDispute_1v1_Actors_Test: Honest actor's moves should always be successful"); // If both actors have run out of moves, we're done. if (numMovesA == 0 && numMovesB == 0) break; diff --git a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol index d26c31bd53240..8c74bee750a67 100644 --- a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol @@ -6,19 +6,18 @@ import { Test } from "forge-std/Test.sol"; import { DisputeGameFactory_Init } from "test/dispute/DisputeGameFactory.t.sol"; import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; -// Contracts -import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol"; -import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; -import { DelayedWETH } from "src/dispute/DelayedWETH.sol"; +// Scripts +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Libraries import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; // Interfaces -import { IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IPermissionedDisputeGame } from "src/dispute/interfaces/IPermissionedDisputeGame.sol"; +import { IPreimageOracle } from "interfaces/dispute/IBigStepper.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; contract PermissionedDisputeGame_Init is DisputeGameFactory_Init { /// @dev The type of the game being tested. @@ -45,29 +44,48 @@ contract PermissionedDisputeGame_Init is DisputeGameFactory_Init { // Set the extra data for the game creation extraData = abi.encode(l2BlockNumber); - AlphabetVM _vm = new AlphabetVM(absolutePrestate, IPreimageOracle(address(new PreimageOracle(0, 0)))); + IPreimageOracle oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (0, 0))) + }) + ); + AlphabetVM _vm = new AlphabetVM(absolutePrestate, oracle); // Use a 7 day delayed WETH to simulate withdrawals. - IDelayedWETH _weth = IDelayedWETH(payable(new DelayedWETH(7 days))); + IDelayedWETH _weth = IDelayedWETH( + DeployUtils.create1({ + _name: "DelayedWETH", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IDelayedWETH.__constructor__, (7 days))) + }) + ); // Deploy an implementation of the fault game gameImpl = IPermissionedDisputeGame( - address( - new PermissionedDisputeGame({ - _gameType: GAME_TYPE, - _absolutePrestate: absolutePrestate, - _maxGameDepth: 2 ** 3, - _splitDepth: 2 ** 2, - _clockExtension: Duration.wrap(3 hours), - _maxClockDuration: Duration.wrap(3.5 days), - _vm: _vm, - _weth: _weth, - _anchorStateRegistry: anchorStateRegistry, - _l2ChainId: 10, - _proposer: PROPOSER, - _challenger: CHALLENGER - }) - ) + DeployUtils.create1({ + _name: "PermissionedDisputeGame", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IPermissionedDisputeGame.__constructor__, + ( + IFaultDisputeGame.GameConstructorParams({ + gameType: GAME_TYPE, + absolutePrestate: absolutePrestate, + maxGameDepth: 2 ** 3, + splitDepth: 2 ** 2, + clockExtension: Duration.wrap(3 hours), + maxClockDuration: Duration.wrap(3.5 days), + vm: _vm, + weth: _weth, + anchorStateRegistry: anchorStateRegistry, + l2ChainId: 10 + }), + PROPOSER, + CHALLENGER + ) + ) + ) + }) ); // Register the game implementation with the factory. disputeGameFactory.setImplementation(GAME_TYPE, gameImpl); @@ -183,6 +201,61 @@ contract PermissionedDisputeGame_Test is PermissionedDisputeGame_Init { vm.stopPrank(); } + /// @dev Tests that step works properly. + function test_step_succeeds() public { + // Give the test contract some ether + vm.deal(CHALLENGER, 1_000 ether); + + vm.startPrank(CHALLENGER, CHALLENGER); + + // Make claims all the way down the tree. + (,,,, Claim disputed,,) = gameProxy.claimData(0); + gameProxy.attack{ value: _getRequiredBond(0) }(disputed, 0, _dummyClaim()); + (,,,, disputed,,) = gameProxy.claimData(1); + gameProxy.attack{ value: _getRequiredBond(1) }(disputed, 1, _dummyClaim()); + (,,,, disputed,,) = gameProxy.claimData(2); + gameProxy.attack{ value: _getRequiredBond(2) }(disputed, 2, _dummyClaim()); + (,,,, disputed,,) = gameProxy.claimData(3); + gameProxy.attack{ value: _getRequiredBond(3) }(disputed, 3, _dummyClaim()); + (,,,, disputed,,) = gameProxy.claimData(4); + gameProxy.attack{ value: _getRequiredBond(4) }(disputed, 4, _changeClaimStatus(_dummyClaim(), VMStatuses.PANIC)); + (,,,, disputed,,) = gameProxy.claimData(5); + gameProxy.attack{ value: _getRequiredBond(5) }(disputed, 5, _dummyClaim()); + (,,,, disputed,,) = gameProxy.claimData(6); + gameProxy.attack{ value: _getRequiredBond(6) }(disputed, 6, _dummyClaim()); + (,,,, disputed,,) = gameProxy.claimData(7); + gameProxy.attack{ value: _getRequiredBond(7) }(disputed, 7, _dummyClaim()); + + // Verify game state before step + assertEq(uint256(gameProxy.status()), uint256(GameStatus.IN_PROGRESS)); + + gameProxy.addLocalData(LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER, 8, 0); + gameProxy.step(8, true, absolutePrestateData, hex""); + + vm.warp(block.timestamp + gameProxy.maxClockDuration().raw() + 1); + gameProxy.resolveClaim(8, 0); + gameProxy.resolveClaim(7, 0); + gameProxy.resolveClaim(6, 0); + gameProxy.resolveClaim(5, 0); + gameProxy.resolveClaim(4, 0); + gameProxy.resolveClaim(3, 0); + gameProxy.resolveClaim(2, 0); + gameProxy.resolveClaim(1, 0); + + gameProxy.resolveClaim(0, 0); + gameProxy.resolve(); + + assertEq(uint256(gameProxy.status()), uint256(GameStatus.CHALLENGER_WINS)); + assertEq(gameProxy.resolvedAt().raw(), block.timestamp); + (, address counteredBy,,,,,) = gameProxy.claimData(0); + assertEq(counteredBy, CHALLENGER); + } + + /// @dev Helper to return a pseudo-random claim + function _dummyClaim() internal view returns (Claim) { + return Claim.wrap(keccak256(abi.encode(gasleft()))); + } + /// @dev Helper to get the required bond for the given claim index. function _getRequiredBond(uint256 _claimIndex) internal view returns (uint256 bond_) { (,,,,, Position parent,) = gameProxy.claimData(_claimIndex); diff --git a/packages/contracts-bedrock/test/dispute/WETH98.t.sol b/packages/contracts-bedrock/test/dispute/WETH98.t.sol index a248e19018854..f207248be19c0 100644 --- a/packages/contracts-bedrock/test/dispute/WETH98.t.sol +++ b/packages/contracts-bedrock/test/dispute/WETH98.t.sol @@ -5,7 +5,8 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; // Contracts -import { WETH98 } from "src/universal/WETH98.sol"; +import { IWETH98 } from "interfaces/universal/IWETH98.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract WETH98_Test is Test { event Approval(address indexed src, address indexed guy, uint256 wad); @@ -13,12 +14,17 @@ contract WETH98_Test is Test { event Deposit(address indexed dst, uint256 wad); event Withdrawal(address indexed src, uint256 wad); - WETH98 public weth; + IWETH98 public weth; address alice; address bob; function setUp() public { - weth = new WETH98(); + weth = IWETH98( + DeployUtils.create1({ + _name: "WETH98", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IWETH98.__constructor__, ())) + }) + ); alice = makeAddr("alice"); bob = makeAddr("bob"); deal(alice, 1 ether); diff --git a/packages/contracts-bedrock/test/governance/MintManager.t.sol b/packages/contracts-bedrock/test/governance/MintManager.t.sol index 4c008863ed691..04255474d7a7a 100644 --- a/packages/contracts-bedrock/test/governance/MintManager.t.sol +++ b/packages/contracts-bedrock/test/governance/MintManager.t.sol @@ -4,13 +4,10 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; -// Contracts -import { GovernanceToken } from "src/governance/GovernanceToken.sol"; -import { MintManager } from "src/governance/MintManager.sol"; - // Interfaces -import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; -import { IMintManager } from "src/governance/interfaces/IMintManager.sol"; +import { IGovernanceToken } from "interfaces/governance/IGovernanceToken.sol"; +import { IMintManager } from "interfaces/governance/IMintManager.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract MintManager_Initializer is CommonTest { address constant owner = address(0x1234); @@ -23,10 +20,20 @@ contract MintManager_Initializer is CommonTest { super.setUp(); vm.prank(owner); - gov = IGovernanceToken(address(new GovernanceToken())); + gov = IGovernanceToken( + DeployUtils.create1({ + _name: "GovernanceToken", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IGovernanceToken.__constructor__, ())) + }) + ); vm.prank(owner); - manager = IMintManager(address(new MintManager(owner, address(gov)))); + manager = IMintManager( + DeployUtils.create1({ + _name: "MintManager", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IMintManager.__constructor__, (owner, address(gov)))) + }) + ); vm.prank(owner); gov.transferOwnership(address(manager)); diff --git a/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol index 08a0c0027763e..06df8d1ea0768 100644 --- a/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol @@ -3,14 +3,13 @@ pragma solidity 0.8.15; import { StdUtils } from "forge-std/StdUtils.sol"; import { Vm } from "forge-std/Vm.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Encoding } from "src/libraries/Encoding.sol"; import { Hashing } from "src/libraries/Hashing.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; contract RelayActor is StdUtils { // Storage slot of the l2Sender @@ -88,7 +87,7 @@ contract RelayActor is StdUtils { } } -contract XDM_MinGasLimits is Bridge_Initializer { +contract XDM_MinGasLimits is CommonTest { RelayActor actor; function init(bool doFail) public virtual { diff --git a/packages/contracts-bedrock/test/invariants/ETHLiquidity.t.sol b/packages/contracts-bedrock/test/invariants/ETHLiquidity.t.sol index 8d1221a792c1b..4d67468107315 100644 --- a/packages/contracts-bedrock/test/invariants/ETHLiquidity.t.sol +++ b/packages/contracts-bedrock/test/invariants/ETHLiquidity.t.sol @@ -10,7 +10,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; +import { IETHLiquidity } from "interfaces/L2/IETHLiquidity.sol"; /// @title ETHLiquidity_User /// @notice Actor contract that interacts with the ETHLiquidity contract. Always pretends to be the diff --git a/packages/contracts-bedrock/test/invariants/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/invariants/FaultDisputeGame.t.sol index 49bf2a106bbf4..705e21bd4cfc9 100644 --- a/packages/contracts-bedrock/test/invariants/FaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/invariants/FaultDisputeGame.t.sol @@ -11,7 +11,7 @@ import "src/dispute/lib/Types.sol"; import "src/dispute/lib/Errors.sol"; // Interfaces -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; contract FaultDisputeGame_Solvency_Invariant is FaultDisputeGame_Init { Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10))); @@ -69,7 +69,7 @@ contract FaultDisputeGame_Solvency_Invariant is FaultDisputeGame_Init { assertEq(DEFAULT_SENDER.balance, type(uint96).max - rootBond); assertEq(address(actor).balance, actor.totalBonded() + rootBond); } else { - revert("unreachable"); + revert("FaultDisputeGame_Solvency_Invariant: unreachable"); } assertEq(address(gameProxy).balance, 0); diff --git a/packages/contracts-bedrock/test/invariants/L2OutputOracle.t.sol b/packages/contracts-bedrock/test/invariants/L2OutputOracle.t.sol index 95eac249a5ba4..fce298d2cebaa 100644 --- a/packages/contracts-bedrock/test/invariants/L2OutputOracle.t.sol +++ b/packages/contracts-bedrock/test/invariants/L2OutputOracle.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; import { Vm } from "forge-std/Vm.sol"; contract L2OutputOracle_Proposer { @@ -33,6 +33,7 @@ contract L2OutputOracle_MonotonicBlockNumIncrease_Invariant is CommonTest { L2OutputOracle_Proposer internal actor; function setUp() public override { + super.enableLegacyContracts(); super.setUp(); // Create a proposer actor. diff --git a/packages/contracts-bedrock/test/invariants/OptimismPortal.t.sol b/packages/contracts-bedrock/test/invariants/OptimismPortal.t.sol index 8e5319d9aeb68..42bf52e1de831 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismPortal.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismPortal.t.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.15; import { StdUtils } from "forge-std/Test.sol"; import { Vm } from "forge-std/Vm.sol"; -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { Constants } from "src/libraries/Constants.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; @@ -88,6 +88,7 @@ contract OptimismPortal_Invariant_Harness is CommonTest { Types.OutputRootProof internal _outputRootProof; function setUp() public virtual override { + super.enableLegacyContracts(); super.setUp(); _defaultTx = Types.WithdrawalTransaction({ diff --git a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol index dec4525a00945..0a870bc651f3d 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol @@ -17,9 +17,9 @@ import "src/dispute/lib/Types.sol"; import "src/libraries/PortalErrors.sol"; // Interfaces -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; contract OptimismPortal2_Depositor is StdUtils, ResourceMetering { Vm internal vm; @@ -96,7 +96,6 @@ contract OptimismPortal2_Invariant_Harness is CommonTest { Types.OutputRootProof internal _outputRootProof; function setUp() public virtual override { - super.enableFaultProofs(); super.setUp(); _defaultTx = Types.WithdrawalTransaction({ diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol index 4ecbafb05969c..d53d2fd29f93c 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol @@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol"; import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol"; import { HandlerGetters } from "./helpers/HandlerGetters.t.sol"; @@ -38,7 +38,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply + fundsInTransit); @@ -61,7 +61,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply); @@ -69,7 +69,7 @@ contract OptimismSuperchainERC20Properties is Test { } /// @custom:invariant many other assertion mode invariants are also defined under - /// `test/invariants/OptimismSuperchainERC20/fuzz/` . + /// `test/invariants/SuperchainERC20/fuzz/` . /// /// since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the /// handler for assertion test failures diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md index 18970855ca468..1c8c90bb0301c 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md @@ -1,5 +1,9 @@ # Supertoken advanced testing +## Note + +This campaign will need to be updated the redesign `OptimismSuperchainERC20` redesign. Please delete this comment once the update is done. + ## Milestones The supertoken ecosystem consists of not just the supertoken contract, but the required changes to other contracts for liquidity to reach the former. @@ -12,8 +16,8 @@ Considering only the supertoken contract is merged into the `develop` branch, an ## Definitions -- *legacy token:* an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping. -- *supertoken:* a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory` +- _legacy token:_ an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping. +- _supertoken:_ a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory` # Ecosystem properties @@ -28,7 +32,7 @@ legend: ## Unit test | id | milestone | description | tested | -| --- | --- | --- | --- | +| --- | ------------------- | ------------------------------------------------------------------------------------------ | ------ | | 0 | Factories | supertoken token address does not depend on the executing chain’s chainID | [ ] | | 1 | Factories | supertoken token address depends on remote token, name, symbol and decimals | [ ] | | 2 | Liquidity Migration | convert() should only allow converting legacy tokens to supertoken and viceversa | [ ] | @@ -40,18 +44,18 @@ legend: ## Valid state | id | milestone | description | tested | -| --- | --- | --- | --- | -| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [x] | -| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [~] | +| --- | --------- | ------------------------------------------------------------------------------ | ------ | +| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [] | +| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [] | ## Variable transition | id | milestone | description | tested | -| --- | --- | --- | --- | -| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [x] | -| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [x] | -| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [x] | -| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [x] | +| --- | ------------------- | ------------------------------------------------------------------------------------------------- | ------ | +| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [] | +| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [] | +| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [] | +| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [] | | 27 | SupERC20 | relayERC20 increases sender's balance in the destination chain exactly by the input amount | [x] | | 11 | SupERC20 | relayERC20 increases the token's totalSupply in the destination chain exactly by the input amount | [ ] | | 12 | Liquidity Migration | supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge | [~] | @@ -63,9 +67,9 @@ legend: ## High level | id | milestone | description | tested | -| --- | --- | --- | --- | -| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] | -| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] | +| --- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] | +| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] | | 19 | Liquidity Migration | sum of supertoken total supply across all chains is always <= to convert(legacy, super)- convert(super, legacy) | [~] | | 20 | SupERC20 | tokens sendERC20-ed on a source chain to a destination chain can be relayERC20-ed on it as long as the source chain is in the dependency set of the destination chain | [ ] | | 21 | Liquidity Migration | sum of supertoken total supply across all chains is = to convert(legacy, super)- convert(super, legacy) when all cross-chain messages are processed | [~] | @@ -76,7 +80,7 @@ As another layer of defense, the following properties are defined which assume b It’s worth noting that these properties will not hold for a live system | id | milestone | description | tested | -| --- | --- | --- | --- | -| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [x] | -| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [x] | +| --- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------ | +| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [] | +| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [] | | 24 | Liquidity Migration | sum of supertoken total supply across all chains is always equal to convert(legacy, super)- convert(super, legacy) | [~] | diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol index 536a4ea7025ae..236c41873b6c1 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.25; import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { ProtocolHandler } from "../handlers/Protocol.t.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; @@ -21,7 +21,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { validateTokenDeployParams(params) { chainId = bound(chainId, 0, MAX_CHAINS - 1); - OptimismSuperchainERC20 supertoken = _deploySupertoken( + SuperchainERC20 supertoken = _deploySupertoken( remoteTokens[params.remoteTokenIndex], WORDS[params.nameIndex], WORDS[params.symbolIndex], @@ -32,100 +32,6 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { compatibleAssert(supertoken.totalSupply() == 0); } - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 22 - /// @custom:property sendERC20 decreases sender balance in source chain and increases receiver balance in - /// destination chain exactly by the input amount - /// @custom:property-id 23 - /// @custom:property sendERC20 decreases total supply in source chain and increases it in destination chain exactly - /// by the input amount - function fuzz_bridgeSupertokenAtomic( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId, - uint256 amount - ) - public - withActor(msg.sender) - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor()); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - uint256 destinationBalanceBefore = destinationToken.balanceOf(recipient); - uint256 destinationSupplyBefore = destinationToken.totalSupply(); - - MESSENGER.setAtomic(true); - vm.prank(currentActor()); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - MESSENGER.setAtomic(false); - uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor()); - uint256 destinationBalanceAfter = destinationToken.balanceOf(recipient); - // no free mint - compatibleAssert( - sourceBalanceBefore + destinationBalanceBefore == sourceBalanceAfter + destinationBalanceAfter - ); - // 22 - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - compatibleAssert(destinationBalanceBefore + amount == destinationBalanceAfter); - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - uint256 destinationSupplyAfter = destinationToken.totalSupply(); - // 23 - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - compatibleAssert(destinationSupplyBefore + amount == destinationSupplyAfter); - } catch { - MESSENGER.setAtomic(false); - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 26 - /// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount - /// @custom:property-id 10 - /// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount - function fuzz_sendERC20( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId, - uint256 amount - ) - public - withActor(msg.sender) - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken)); - uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor()); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - - vm.prank(currentActor()); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - (, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt); - ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount); - // 26 - uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor()); - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - // 10 - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - } catch { - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - /// @custom:property-id 11 /// @custom:property relayERC20 increases the token's totalSupply in the destination chain exactly by the input /// amount @@ -135,7 +41,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid function fuzz_relayERC20(uint256 messageIndex) external { MockL2ToL2CrossDomainMessenger.CrossChainMessage memory messageToRelay = MESSENGER.messageQueue(messageIndex); - OptimismSuperchainERC20 destinationToken = OptimismSuperchainERC20(messageToRelay.crossDomainMessageSender); + SuperchainERC20 destinationToken = SuperchainERC20(messageToRelay.crossDomainMessageSender); uint256 destinationSupplyBefore = destinationToken.totalSupply(); uint256 destinationBalanceBefore = destinationToken.balanceOf(messageToRelay.recipient); @@ -156,56 +62,4 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { compatibleAssert(false); } } - - /// @custom:property-id 8 - /// @custom:property calls to sendERC20 with a value of zero dont modify accounting - // @notice is a subset of fuzz_sendERC20, so we'll just call it - // instead of re-implementing it. Keeping the function for visibility of the property. - function fuzz_sendZeroDoesNotModifyAccounting( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId - ) - external - { - fuzz_sendERC20(fromIndex, recipientIndex, destinationChainId, 0); - } - - /// @custom:property-id 9 - /// @custom:property calls to relayERC20 with a value of zero dont modify accounting - /// @custom:property-id 7 - /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid - /// @notice cant call fuzz_RelayERC20 internally since that pops a - /// random message, which we cannot guarantee has a value of zero - function fuzz_relayZeroDoesNotModifyAccounting( - uint256 fromIndex, - uint256 recipientIndex - ) - external - withActor(msg.sender) - { - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 token = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - uint256 balanceSenderBefore = token.balanceOf(currentActor()); - uint256 balanceRecipientBefore = token.balanceOf(recipient); - uint256 supplyBefore = token.totalSupply(); - - MESSENGER.setCrossDomainMessageSender(address(token)); - vm.prank(address(MESSENGER)); - try token.relayERC20(currentActor(), recipient, 0) { - MESSENGER.setCrossDomainMessageSender(address(0)); - } catch { - // should not revert because of 7, and if it *does* revert, I want the test suite - // to discard the sequence instead of potentially getting another - // error due to the crossDomainMessageSender being manually set - compatibleAssert(false); - } - uint256 balanceSenderAfter = token.balanceOf(currentActor()); - uint256 balanceRecipeintAfter = token.balanceOf(recipient); - uint256 supplyAfter = token.totalSupply(); - compatibleAssert(balanceSenderBefore == balanceSenderAfter); - compatibleAssert(balanceRecipientBefore == balanceRecipeintAfter); - compatibleAssert(supplyBefore == supplyAfter); - } } diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index 90cad38baa990..aa3eaaa931340 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -10,78 +10,6 @@ import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { using EnumerableMap for EnumerableMap.Bytes32ToUintMap; - /// @custom:property-id 7 - /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid - /// @notice this ensures actors cant simply call relayERC20 and get tokens, no matter the system state - /// but there's still some possible work on how hard we can bork the system state with handlers calling - /// the L2ToL2CrossDomainMessenger or bridge directly (pending on non-atomic bridging) - function fuzz_relayERC20( - uint256 tokenIndex, - address sender, - address crossDomainMessageSender, - address recipient, - uint256 amount - ) - external - { - MESSENGER.setCrossDomainMessageSender(crossDomainMessageSender); - address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]; - vm.prank(sender); - try OptimismSuperchainERC20(token).relayERC20(sender, recipient, amount) { - MESSENGER.setCrossDomainMessageSender(address(0)); - compatibleAssert(sender == address(MESSENGER)); - compatibleAssert(crossDomainMessageSender == token); - // this increases the supply across chains without a call to - // `mint` by the MESSENGER, so it kind of breaks an invariant, but - // let's walk around that: - bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); - (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); - ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); - } catch { - compatibleAssert(sender != address(MESSENGER) || crossDomainMessageSender != token); - MESSENGER.setCrossDomainMessageSender(address(0)); - } - } - - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 26 - /// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount - /// @custom:property-id 10 - /// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount - function fuzz_sendERC20( - address sender, - address recipient, - uint256 fromIndex, - uint256 destinationChainId, - uint256 amount - ) - public - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken)); - uint256 sourceBalanceBefore = sourceToken.balanceOf(sender); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - - vm.prank(sender); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - (, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt); - ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount); - // 26 - uint256 sourceBalanceAfter = sourceToken.balanceOf(sender); - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - // 10 - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - } catch { - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - /// @custom:property-id 12 /// @custom:property supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge function fuzz_mint(uint256 tokenIndex, address to, address sender, uint256 amount) external { @@ -89,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); vm.prank(sender); - try OptimismSuperchainERC20(token).mint(to, amount) { + try OptimismSuperchainERC20(token).crosschainMint(to, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); @@ -105,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); vm.prank(sender); - try OptimismSuperchainERC20(token).burn(from, amount) { + try OptimismSuperchainERC20(token).crosschainBurn(from, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol index 921495b467ab1..490a38bec1cff 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { index = bound(index, 0, allSuperTokens.length - 1); address addr = allSuperTokens[index]; vm.prank(BRIDGE); - OptimismSuperchainERC20(addr).mint(currentActor(), amount); + OptimismSuperchainERC20(addr).crosschainMint(currentActor(), amount); // currentValue will be zero if key is not present (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); @@ -151,27 +151,29 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { uint256 chainId ) internal - returns (OptimismSuperchainERC20 supertoken) + returns (OptimismSuperchainERC20 supertoken_) { // this salt would be used in production. Tokens sharing it will be bridgable with each other bytes32 realSalt = keccak256(abi.encode(remoteToken, name, symbol, decimals)); // Foundry invariant erroneously show other unrelated invariant breaking // when this deployment fails due to a create2 collision, so we revert eagerly instead - require(MESSENGER.superTokenAddresses(chainId, realSalt) == address(0), "skip duplicate deployment"); + require( + MESSENGER.superTokenAddresses(chainId, realSalt) == address(0), "ProtocolHandler: skip duplicate deployment" + ); // what we use in the tests to walk around two contracts needing two different addresses // tbf we could be using CREATE1, but this feels more verbose bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId)); - supertoken = OptimismSuperchainERC20( + supertoken_ = OptimismSuperchainERC20( address( - // TODO: Use the SuperchainERC20 Beacon Proxy + // TODO: Use the OptimismSuperchainERC20 Beacon Proxy new ERC1967Proxy{ salt: hackySalt }( address(superchainERC20Impl), abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) ) ) ); - MESSENGER.registerSupertoken(realSalt, chainId, address(supertoken)); - allSuperTokens.push(address(supertoken)); + MESSENGER.registerSupertoken(realSalt, chainId, address(supertoken_)); + allSuperTokens.push(address(supertoken_)); } } diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/HandlerGetters.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/HandlerGetters.t.sol index b081aa48c6bbc..d072dc92165be 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/HandlerGetters.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/HandlerGetters.t.sol @@ -7,16 +7,16 @@ import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableM contract HandlerGetters is ProtocolHandler { using EnumerableMap for EnumerableMap.Bytes32ToUintMap; - function deploySaltsLength() external view returns (uint256 length) { + function deploySaltsLength() external view returns (uint256 length_) { return ghost_totalSupplyAcrossChains.length(); } - function totalSupplyAcrossChainsAtIndex(uint256 index) external view returns (bytes32 salt, uint256 supply) { - return ghost_totalSupplyAcrossChains.at(index); + function totalSupplyAcrossChainsAtIndex(uint256 _index) external view returns (bytes32 salt_, uint256 supply_) { + return ghost_totalSupplyAcrossChains.at(_index); } - function tokensInTransitForDeploySalt(bytes32 salt) external view returns (uint256 amount) { - (, amount) = ghost_tokensInTransit.tryGet(salt); - return amount; + function tokensInTransitForDeploySalt(bytes32 _salt) external view returns (uint256 amount_) { + (, amount_) = ghost_tokensInTransit.tryGet(_salt); + return amount_; } } diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol index 6eb1c30e67994..94f275e26551a 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; contract MockL2ToL2CrossDomainMessenger { @@ -41,9 +41,9 @@ contract MockL2ToL2CrossDomainMessenger { ) external view - returns (OptimismSuperchainERC20) + returns (SuperchainERC20) { - return OptimismSuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); + return SuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); } function setCrossDomainMessageSender(address sender) external { @@ -89,7 +89,7 @@ contract MockL2ToL2CrossDomainMessenger { function sendMessage(uint256 chainId, address, /*recipient*/ bytes calldata data) external { address crossChainRecipient = superTokenAddresses[chainId][superTokenInitDeploySalts[msg.sender]]; if (crossChainRecipient == msg.sender) { - require(false, "same chain"); + require(false, "MockL2ToL2CrossDomainMessenger: same chain"); } (address recipient, uint256 amount) = _decodePayload(data); @@ -112,7 +112,7 @@ contract MockL2ToL2CrossDomainMessenger { // Internal helpers // //////////////////////// - function _decodePayload(bytes calldata payload) internal pure returns (address recipient, uint256 amount) { - (, recipient, amount) = abi.decode(payload[4:], (address, address, uint256)); + function _decodePayload(bytes calldata payload) internal pure returns (address recipient_, uint256 amount_) { + (, recipient_, amount_) = abi.decode(payload[4:], (address, address, uint256)); } } diff --git a/packages/contracts-bedrock/test/invariants/ResourceMetering.t.sol b/packages/contracts-bedrock/test/invariants/ResourceMetering.t.sol index 4652f9b9e36e8..49793f2adf14a 100644 --- a/packages/contracts-bedrock/test/invariants/ResourceMetering.t.sol +++ b/packages/contracts-bedrock/test/invariants/ResourceMetering.t.sol @@ -8,7 +8,7 @@ import { StdInvariant } from "forge-std/StdInvariant.sol"; import { Arithmetic } from "src/libraries/Arithmetic.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { Constants } from "src/libraries/Constants.sol"; import { InvariantTest } from "test/invariants/InvariantTest.sol"; diff --git a/packages/contracts-bedrock/test/invariants/SafeCall.t.sol b/packages/contracts-bedrock/test/invariants/SafeCall.t.sol index 0a70c3ddd9062..2de6183bd130c 100644 --- a/packages/contracts-bedrock/test/invariants/SafeCall.t.sol +++ b/packages/contracts-bedrock/test/invariants/SafeCall.t.sol @@ -103,10 +103,7 @@ contract SafeCaller_Actor is StdUtils { vm.expectCallMinGas(to, value, minGas, hex""); bool success = SafeCall.call( - msg.sender, - gas, - value, - abi.encodeWithSelector(SafeCall_Succeeds_Invariants.performSafeCallMinGas.selector, to, minGas) + msg.sender, gas, value, abi.encodeCall(SafeCall_Succeeds_Invariants.performSafeCallMinGas, (to, minGas)) ); if (success && FAILS) numCalls++; diff --git a/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol index 1e761b7ea1621..bb6ee569da146 100644 --- a/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol @@ -6,22 +6,12 @@ import { StdUtils } from "forge-std/Test.sol"; import { Vm } from "forge-std/Vm.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; -// Libraries -import { Predeploys } from "src/libraries/Predeploys.sol"; - // Interfaces -import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import { ISuperchainWETH } from "interfaces/L2/ISuperchainWETH.sol"; /// @title SuperchainWETH_User /// @notice Actor contract that interacts with the SuperchainWETH contract. contract SuperchainWETH_User is StdUtils { - /// @notice Cross domain message data. - struct MessageData { - bytes32 id; - uint256 amount; - } - /// @notice Flag to indicate if the test has failed. bool public failed = false; @@ -31,12 +21,6 @@ contract SuperchainWETH_User is StdUtils { /// @notice The SuperchainWETH contract. ISuperchainWETH internal weth; - /// @notice Mapping of sent messages. - mapping(bytes32 => bool) internal sent; - - /// @notice Array of unrelayed messages. - MessageData[] internal unrelayed; - /// @param _vm The Vm contract. /// @param _weth The SuperchainWETH contract. /// @param _balance The initial balance of the contract. @@ -76,72 +60,6 @@ contract SuperchainWETH_User is StdUtils { failed = true; } } - - /// @notice Send ERC20 tokens to another chain. - /// @param _amount The amount of ERC20 tokens to send. - /// @param _chainId The chain ID to send the tokens to. - /// @param _messageId The message ID. - function sendERC20(uint256 _amount, uint256 _chainId, bytes32 _messageId) public { - // Make sure we aren't reusing a message ID. - if (sent[_messageId]) { - return; - } - - // Bound send amount to our WETH balance. - _amount = bound(_amount, 0, weth.balanceOf(address(this))); - - // Prevent receiving chain ID from being the same as the current chain ID. - _chainId = _chainId == block.chainid ? _chainId + 1 : _chainId; - - // Send the amount. - try weth.sendERC20(address(this), _amount, _chainId) { - // Success. - } catch { - failed = true; - } - - // Mark message as sent. - sent[_messageId] = true; - unrelayed.push(MessageData({ id: _messageId, amount: _amount })); - } - - /// @notice Relay a message from another chain. - function relayMessage(uint256 _source) public { - // Make sure there are unrelayed messages. - if (unrelayed.length == 0) { - return; - } - - // Grab the latest unrelayed message. - MessageData memory message = unrelayed[unrelayed.length - 1]; - - // Simulate the cross-domain message. - // Make sure the cross-domain message sender is set to this contract. - vm.mockCall( - Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSender, ()), - abi.encode(address(weth)) - ); - - // Simulate the cross-domain message source to any chain. - vm.mockCall( - Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSource, ()), - abi.encode(_source) - ); - - // Prank the relayERC20 function. - // Balance will just go back to our own account. - vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - try weth.relayERC20(address(this), address(this), message.amount) { - // Success. - } catch { - failed = true; - } - - // Remove the message from the unrelayed list. - unrelayed.pop(); - } } /// @title SuperchainWETH_SendSucceeds_Invariant @@ -167,11 +85,9 @@ contract SuperchainWETH_SendSucceeds_Invariant is CommonTest { targetContract(address(actor)); // Set the target selectors. - bytes4[] memory selectors = new bytes4[](4); + bytes4[] memory selectors = new bytes4[](2); selectors[0] = actor.deposit.selector; selectors[1] = actor.withdraw.selector; - selectors[2] = actor.sendERC20.selector; - selectors[3] = actor.relayMessage.selector; FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors }); targetSelector(selector); } diff --git a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol index 1321499462b71..68add058f60dd 100644 --- a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol @@ -2,17 +2,27 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; -import { SystemConfig } from "src/L1/SystemConfig.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { Proxy } from "src/universal/Proxy.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; import { Constants } from "src/libraries/Constants.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract SystemConfig_GasLimitBoundaries_Invariant is Test { ISystemConfig public config; function setUp() external { - Proxy proxy = new Proxy(msg.sender); - ISystemConfig configImpl = ISystemConfig(address(new SystemConfig())); + IProxy proxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender))) + }) + ); + ISystemConfig configImpl = ISystemConfig( + DeployUtils.create1({ + _name: "SystemConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfig.__constructor__, ())) + }) + ); vm.prank(msg.sender); proxy.upgradeToAndCall( diff --git a/packages/contracts-bedrock/test/kontrol/README.md b/packages/contracts-bedrock/test/kontrol/README.md index 25660756963ce..cf809eb65b48b 100644 --- a/packages/contracts-bedrock/test/kontrol/README.md +++ b/packages/contracts-bedrock/test/kontrol/README.md @@ -48,10 +48,9 @@ The directory is structured as follows ### Installation -1. `cd` to the root of this repo. -2. Install Foundry by running `just install-foundry`. This installs `foundryup`, the foundry toolchain installer, then installs the required foundry version. -3. Install Kontrol by running `just install-kontrol`. This installs `kup`, the package manager for RV tools, then installs the required kontrol version. -4. Install Docker. +1. Make sure that the dependencies for the Optimism Monorepo are installed with `mise`. +1. Install [`kup`](https://github.com/runtimeverification/k/releases/tag/v7.1.180). +1. Use `kup` to [install `kontrol`](https://github.com/runtimeverification/kontrol?tab=readme-ov-file#fast-installation) ## Usage @@ -91,7 +90,7 @@ Use the [`run-kontrol.sh`](./scripts/run-kontrol.sh) script to runs the proofs i The `run-kontrol.sh` script supports three modes of proof execution: - `container`: Runs the proofs using the same Docker image used in CI. This is the default execution mode—if no arguments are provided, the proofs will be executed in this mode. -- `local`: Runs the proofs with your local Kontrol install, and enforces that the Kontrol version matches the one used in CI, which is specified in [`versions.json`](../../../../versions.json). +- `local`: Runs the proofs with your local Kontrol install, and enforces that the Kontrol version matches the one used in CI, which is specified in [`mise.toml`](../../../../mise.toml). - `dev`: Run the proofs with your local Kontrol install, without enforcing any version in particular. The intended use case is proof development and related matters. It also supports two methods for specifying which tests to execute: @@ -115,12 +114,6 @@ The `runKontrolDeployment` function of [`KontrolDeployment`](./deployment/Kontro Once new deployment steps have been added to `runKontrolDeployment` the state-diff files have to [be rebuilt](#build-deployment-summary). -#### Include existing tests on the new state-diff recorded bytecode - -The next step is to include tests for the newly included state updates in [`DeploymentSummary.t.sol`](deployment/DeploymentSummary.t.sol). These tests inherit the tests from [`test`](../L1) of the contracts deployed by `runKontrolDeployment`. This ensures that deployment steps were implemented correctly and that the state updates are correct. - -It might be necessary to set some of the existing tests from [`test`](../L1) as virtual because they can't be executed as is. See [`DeploymentSummary.t.sol`](deployment/DeploymentSummary.t.sol) for more concrete examples. - #### Write the proof Write your proof in a `.k.sol` file in the [`proofs`](./proofs/) folder, which is the `test` directory used by the `kprove` profile to run the proofs (see [Deployment Summary Process](#deployment-summary-process)). The name of the new proofs should start with `prove` (or `check`) instead of `test` to avoid `forge test` running them. The reason for this is that if Kontrol cheatcodes (see [Kontrol's own cheatcodes](https://github.com/runtimeverification/kontrol-cheatcodes/blob/master/src/KontrolCheats.sol)) are used in a test, it will not be runnable by `forge`. Currently, none of the tests are using custom Kontrol cheatcodes, but this is something to bear in mind. @@ -128,8 +121,8 @@ Write your proof in a `.k.sol` file in the [`proofs`](./proofs/) folder, which i To reference the correct addresses for writing the tests, first import the signatures as in this example: ```solidity -import { IOptimismPortal as OptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { ISuperchainConfig as SuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { IOptimismPortal as OptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { ISuperchainConfig as SuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; ``` Declare the correspondent variables and cast the correct signatures to the correct addresses: @@ -143,8 +136,7 @@ function setUp() public { superchainConfig = SuperchainConfig(superchainConfigProxyAddress); } ``` - -Note that the names of the addresses come from [`DeploymentSummary.t.sol`](deployment/DeploymentSummary.t.sol) and are automatically generated by the [`make-summary-deployment.sh`](./scripts/make-summary-deployment.sh) script. +Note that the names of the addresses are automatically generated by the [`make-summary-deployment.sh`](./scripts/make-summary-deployment.sh) script. #### Add your test to [`run-kontrol.sh`](./scripts/run-kontrol.sh) @@ -155,7 +147,6 @@ As described in [Execute Proofs](#execute-proofs), there's a `script` mode for s ### Assumptions 1. A critical invariant of the `KontrolDeployment.sol` contract is that it stays in sync with the original `Deploy.s.sol` contract. - Currently, this is partly enforced by running some of the standard post-`setUp` deployment assertions in `DeploymentSummary.t.sol`. A more rigorous approach may be to leverage the `ChainAssertions` library, but more investigation is required to determine if this is feasible without large changes to the deploy script. 2. Size of `bytes[]` arguments. In [`OptimismPortal.k.sol`](./proofs/OptimismPortal.k.sol), the `prove_proveWithdrawalTransaction_paused` proof is broken down into 11 different proofs, each corresponding to a different size of the `_withdrawalProof` argument, which is of type `bytes[]`. We execute the same logic for lengths of `_withdrawalProof` ranging from 0 to 10, setting the length of each symbolic `bytes` element to 600. diff --git a/packages/contracts-bedrock/test/kontrol/deployment/KontrolDeployment.sol b/packages/contracts-bedrock/test/kontrol/deployment/KontrolDeployment.sol deleted file mode 100644 index 7853b0a4d3dd8..0000000000000 --- a/packages/contracts-bedrock/test/kontrol/deployment/KontrolDeployment.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { Deploy } from "scripts/deploy/Deploy.s.sol"; - -contract KontrolDeployment is Deploy { - function runKontrolDeployment() public { - runWithStateDiff(); - } - - function runKontrolDeploymentFaultProofs() public { - cfg.setUseFaultProofs(true); - runWithStateDiff(); - } -} diff --git a/packages/contracts-bedrock/test/kontrol/pausability-lemmas.md b/packages/contracts-bedrock/test/kontrol/pausability-lemmas.md index 890c6cb957727..2d6a747185ac0 100644 --- a/packages/contracts-bedrock/test/kontrol/pausability-lemmas.md +++ b/packages/contracts-bedrock/test/kontrol/pausability-lemmas.md @@ -202,17 +202,17 @@ The summary lemma is as follows, with commentary inlined: // Various well-formedness constraints. In particular, the maxBytesLength-related ones are present to // remove various chops that would otherwise creep into the execution, and are reasonable since byte // arrays in actual programs would never reach that size. - andThenBool 0 <=Int PCOUNT - andThenBool 0 <=Int LENGTH andThenBool LENGTH &2 echo "Execution modes:" echo " container Run in docker container. Reproduce CI execution. (Default)" 1>&2 - echo " local Run locally, enforces registered versions.json version for better reproducibility. (Recommended)" 1>&2 + echo " local Run locally, enforces registered mise.toml version for better reproducibility. (Recommended)" 1>&2 echo " dev Run locally, does NOT enforce registered version. (Useful for developing with new versions and features)" 1>&2 echo "" 1>&2 echo "Tests executed:" @@ -28,7 +28,7 @@ usage_make_summary() { echo "" 1>&2 echo "Execution modes:" echo " container Run in docker container. Reproduce CI execution. (Default)" 1>&2 - echo " local Run locally, enforces registered versions.json version for better reproducibility. (Recommended)" 1>&2 + echo " local Run locally, enforces registered mise.toml version for better reproducibility. (Recommended)" 1>&2 echo " dev Run locally, does NOT enforce registered version. (Useful for developing with new versions and features)" 1>&2 exit 0 } @@ -43,7 +43,7 @@ export CONTAINER_NAME=kontrol-tests if [ "$KONTROL_FP_DEPLOYMENT" = true ]; then export CONTAINER_NAME=kontrol-fp-tests fi -KONTROLRC=$(jq -r .kontrol < "$WORKSPACE_DIR/../../versions.json") +KONTROLRC=$(yq '.tools.kontrol' "$WORKSPACE_DIR/../../mise.toml") export KONTROL_RELEASE=$KONTROLRC export LOCAL=false export SCRIPT_TESTS=false @@ -155,7 +155,7 @@ start_docker () { --rm \ --interactive \ --detach \ - --env FOUNDRY_PROFILE="$FOUNDRY_PROFILE" \ + --env FOUNDRY_PROFILE="${FOUNDRY_PROFILE-default}" \ --workdir /home/user/workspace \ runtimeverificationinc/kontrol:ubuntu-jammy-"$KONTROL_RELEASE" diff --git a/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh b/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh index 7d7b8da150f34..c1cd862868838 100755 --- a/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh +++ b/packages/contracts-bedrock/test/kontrol/scripts/make-summary-deployment.sh @@ -1,8 +1,6 @@ #!/bin/bash set -euo pipefail -export FOUNDRY_PROFILE=kdeploy - SCRIPT_HOME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" # shellcheck source=/dev/null source "$SCRIPT_HOME/common.sh" @@ -50,10 +48,8 @@ fi conditionally_start_docker CONTRACT_NAMES=deployments/kontrol.json -SCRIPT_SIG="runKontrolDeployment()" if [ "$KONTROL_FP_DEPLOYMENT" = true ]; then CONTRACT_NAMES=deployments/kontrol-fp.json - SCRIPT_SIG="runKontrolDeploymentFaultProofs()" fi # Sender just needs to be anything but the default sender (0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38) @@ -61,7 +57,7 @@ fi # Conflicts with other stuff that happens inside of Kontrol and leads to errors that are hard to debug DEPLOY_CONFIG_PATH=deploy-config/hardhat.json \ DEPLOYMENT_OUTFILE="$CONTRACT_NAMES" \ - forge script --sender 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -vvv test/kontrol/deployment/KontrolDeployment.sol:KontrolDeployment --sig $SCRIPT_SIG + forge script --sender 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -vvv scripts/deploy/Deploy.s.sol:Deploy --sig runWithStateDiff echo "Created state diff json" # Clean and store the state diff json in snapshots/state-diff/Kontrol-Deploy.json @@ -85,7 +81,7 @@ if [ "$KONTROL_FP_DEPLOYMENT" = true ]; then fi copy_to_docker # Copy the newly generated files to the docker container -run kontrol load-state-diff $SUMMARY_NAME snapshots/state-diff/$STATEDIFF --contract-names $CONTRACT_NAMES --output-dir $SUMMARY_DIR --license $LICENSE +run kontrol load-state --from-state-diff $SUMMARY_NAME snapshots/state-diff/$STATEDIFF --contract-names $CONTRACT_NAMES --output-dir $SUMMARY_DIR --license $LICENSE if [ "$LOCAL" = false ]; then # Sync Snapshot updates to the host docker cp "$CONTAINER_NAME:/home/user/workspace/$SUMMARY_DIR" "$WORKSPACE_DIR/$SUMMARY_DIR/.." diff --git a/packages/contracts-bedrock/test/kontrol/scripts/run-kontrol.sh b/packages/contracts-bedrock/test/kontrol/scripts/run-kontrol.sh index bbaaafdb561f7..a1487eecd867d 100755 --- a/packages/contracts-bedrock/test/kontrol/scripts/run-kontrol.sh +++ b/packages/contracts-bedrock/test/kontrol/scripts/run-kontrol.sh @@ -16,10 +16,11 @@ kontrol_build() { notif "Kontrol Build" # shellcheck disable=SC2086 run kontrol build \ - --verbose \ --require $lemmas \ --module-import $module \ - $rekompile + --no-metadata \ + ${rekompile} \ + ${regen} return $? } @@ -36,9 +37,15 @@ kontrol_prove() { $break_on_calls \ $break_every_step \ $tests \ - --init-node-from $state_diff \ - --kore-rpc-command 'kore-rpc-booster --equation-max-recursion 100' \ - --xml-test-report + --init-node-from-diff $state_diff \ + --kore-rpc-command 'kore-rpc-booster --no-post-exec-simplify --equation-max-recursion 100 --equation-max-iterations 1000' \ + --xml-test-report \ + --maintenance-rate 16 \ + --assume-defined \ + --no-log-rewrites \ + --smt-timeout 16000 \ + --smt-retry-limit 0 \ + --no-stack-checks return $? } @@ -107,10 +114,9 @@ lemmas=test/kontrol/pausability-lemmas.md base_module=PAUSABILITY-LEMMAS module=OptimismPortalKontrol:$base_module rekompile=--rekompile -rekompile= +# rekompile= regen=--regen -# shellcheck disable=SC2034 -regen= +# regen= ################################# # Tests to symbolically execute # @@ -201,13 +207,13 @@ get_log_results # Now you can use ${results[0]} and ${results[1]} # to check the results of kontrol_build and kontrol_prove, respectively -if [ ${results[0]} -ne 0 ] && [ ${results[1]} -ne 0 ]; then +if [ "${results[0]}" -ne 0 ] && [ "${results[1]}" -ne 0 ]; then echo "Kontrol Build and Prove Failed" exit 1 -elif [ ${results[0]} -ne 0 ]; then +elif [ "${results[0]}" -ne 0 ]; then echo "Kontrol Build Failed" exit 1 -elif [ ${results[1]} -ne 0 ]; then +elif [ "${results[1]}" -ne 0 ]; then echo "Kontrol Prove Failed" exit 2 else diff --git a/packages/contracts-bedrock/test/legacy/DeployerWhitelist.t.sol b/packages/contracts-bedrock/test/legacy/DeployerWhitelist.t.sol index ab624df2b3227..2a0f47920532d 100644 --- a/packages/contracts-bedrock/test/legacy/DeployerWhitelist.t.sol +++ b/packages/contracts-bedrock/test/legacy/DeployerWhitelist.t.sol @@ -5,14 +5,25 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; // Target contract -import { DeployerWhitelist } from "src/legacy/DeployerWhitelist.sol"; +import { IDeployerWhitelist } from "interfaces/legacy/IDeployerWhitelist.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract DeployerWhitelist_Test is Test { - DeployerWhitelist list; + IDeployerWhitelist list; + address owner = address(12345); + + event OwnerChanged(address oldOwner, address newOwner); + event WhitelistDisabled(address oldOwner); + event WhitelistStatusChanged(address deployer, bool whitelisted); /// @dev Sets up the test suite. function setUp() public { - list = new DeployerWhitelist(); + list = IDeployerWhitelist( + DeployUtils.create1({ + _name: "DeployerWhitelist", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IDeployerWhitelist.__constructor__, ())) + }) + ); } /// @dev Tests that `owner` is initialized to the zero address. @@ -21,10 +32,102 @@ contract DeployerWhitelist_Test is Test { } /// @dev Tests that `setOwner` correctly sets the contract owner. - function test_storageSlots_succeeds() external { - vm.prank(list.owner()); - list.setOwner(address(1)); + function test_setOwner_succeeds(address _owner) external { + vm.store(address(list), bytes32(uint256(0)), bytes32(uint256(uint160(owner)))); + assertEq(list.owner(), owner); + _owner = address(uint160(bound(uint160(_owner), 1, type(uint160).max))); + + vm.prank(owner); + vm.expectEmit(true, true, true, true); + emit OwnerChanged(owner, _owner); + list.setOwner(_owner); + + assertEq(list.owner(), _owner); + } + + /// @dev Tests that `setOwner` reverts when the caller is not the owner. + function test_setOwner_callerNotOwner_reverts(address _caller, address _owner) external { + vm.store(address(list), bytes32(uint256(0)), bytes32(uint256(uint160(owner)))); + assertEq(list.owner(), owner); + + vm.assume(_caller != owner); + + vm.prank(_caller); + vm.expectRevert(bytes("DeployerWhitelist: function can only be called by the owner of this contract")); + list.setOwner(_owner); + } + + /// @dev Tests that `setOwner` reverts when the new owner is the zero address. + function test_setOwner_zeroAddress_reverts() external { + vm.store(address(list), bytes32(uint256(0)), bytes32(uint256(uint160(owner)))); + assertEq(list.owner(), owner); + + vm.prank(owner); + vm.expectRevert(bytes("DeployerWhitelist: can only be disabled via enableArbitraryContractDeployment")); + list.setOwner(address(0)); + } + + /// @dev Tests that `enableArbitraryContractDeployment` correctly disables the whitelist. + function test_enableArbitraryContractDeployment_succeeds() external { + vm.store(address(list), bytes32(uint256(0)), bytes32(uint256(uint160(owner)))); + assertEq(list.owner(), owner); + + vm.prank(owner); + vm.expectEmit(true, true, true, true); + emit WhitelistDisabled(owner); + list.enableArbitraryContractDeployment(); + + assertEq(list.owner(), address(0)); + + // Any address is allowed to deploy contracts even if they are not whitelisted + assertEq(list.whitelist(address(1)), false); + assertEq(list.isDeployerAllowed(address(1)), true); + } + + /// @dev Tests that `enableArbitraryContractDeployment` reverts when the caller is not the owner. + function test_enableArbitraryContractDeployment_callerNotOwner_reverts(address _caller) external { + vm.store(address(list), bytes32(uint256(0)), bytes32(uint256(uint160(owner)))); + assertEq(list.owner(), owner); + + vm.assume(_caller != owner); + + vm.prank(_caller); + vm.expectRevert(bytes("DeployerWhitelist: function can only be called by the owner of this contract")); + list.enableArbitraryContractDeployment(); + } + + /// @dev Tests that `setWhitelistedDeployer` correctly sets the whitelist status of a deployer. + function test_setWhitelistedDeployer_succeeds(address _deployer, bool _isWhitelisted) external { + vm.store(address(list), bytes32(uint256(0)), bytes32(uint256(uint160(owner)))); + assertEq(list.owner(), owner); + + vm.prank(owner); + vm.expectEmit(true, true, true, true); + emit WhitelistStatusChanged(_deployer, _isWhitelisted); + list.setWhitelistedDeployer(_deployer, _isWhitelisted); + + assertEq(list.whitelist(_deployer), _isWhitelisted); + + // _deployer is whitelisted or not (and arbitrary contract deployment is not enabled) + assertNotEq(list.owner(), address(0)); + assertEq(list.isDeployerAllowed(_deployer), _isWhitelisted); + } + + /// @dev Tests that `setWhitelistedDeployer` reverts when the caller is not the owner. + function test_setWhitelistedDeployer_callerNotOwner_reverts( + address _caller, + address _deployer, + bool _isWhitelisted + ) + external + { + vm.store(address(list), bytes32(uint256(0)), bytes32(uint256(uint160(owner)))); + assertEq(list.owner(), owner); + + vm.assume(_caller != owner); - assertEq(bytes32(uint256(1)), vm.load(address(list), bytes32(uint256(0)))); + vm.prank(_caller); + vm.expectRevert(bytes("DeployerWhitelist: function can only be called by the owner of this contract")); + list.setWhitelistedDeployer(_deployer, _isWhitelisted); } } diff --git a/packages/contracts-bedrock/test/legacy/L1BlockNumber.t.sol b/packages/contracts-bedrock/test/legacy/L1BlockNumber.t.sol index 633a324e40435..2c97602704426 100644 --- a/packages/contracts-bedrock/test/legacy/L1BlockNumber.t.sol +++ b/packages/contracts-bedrock/test/legacy/L1BlockNumber.t.sol @@ -4,24 +4,30 @@ pragma solidity 0.8.15; // Testing import { Test } from "forge-std/Test.sol"; -// Contracts -import { L1BlockNumber } from "src/legacy/L1BlockNumber.sol"; -import { L1Block } from "src/L2/L1Block.sol"; - // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +// Interfaces +import { IL1BlockNumber } from "interfaces/legacy/IL1BlockNumber.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; contract L1BlockNumberTest is Test { - L1Block lb; - L1BlockNumber bn; + IL1Block lb; + IL1BlockNumber bn; uint64 constant number = 99; /// @dev Sets up the test suite. function setUp() external { - vm.etch(Predeploys.L1_BLOCK_ATTRIBUTES, address(new L1Block()).code); - lb = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES); - bn = new L1BlockNumber(); + vm.etch(Predeploys.L1_BLOCK_ATTRIBUTES, vm.getDeployedCode("L1Block.sol:L1Block")); + lb = IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES); + bn = IL1BlockNumber( + DeployUtils.create1({ + _name: "L1BlockNumber", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1BlockNumber.__constructor__, ())) + }) + ); vm.prank(lb.DEPOSITOR_ACCOUNT()); lb.setL1BlockValues({ @@ -43,7 +49,7 @@ contract L1BlockNumberTest is Test { /// @dev Tests that `fallback` is correctly dispatched. function test_fallback_succeeds() external { - (bool success, bytes memory ret) = address(bn).call(hex""); + (bool success, bytes memory ret) = address(bn).call(hex"11"); assertEq(success, true); assertEq(ret, abi.encode(number)); } diff --git a/packages/contracts-bedrock/test/legacy/L1ChugSplashProxy.t.sol b/packages/contracts-bedrock/test/legacy/L1ChugSplashProxy.t.sol new file mode 100644 index 0000000000000..28d9e58ed8a74 --- /dev/null +++ b/packages/contracts-bedrock/test/legacy/L1ChugSplashProxy.t.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +// Testing utilities +import { Test } from "forge-std/Test.sol"; +import { VmSafe } from "forge-std/Vm.sol"; + +// Target contract +import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +contract Owner { + bool public isUpgrading; + + function setIsUpgrading(bool _isUpgrading) public { + isUpgrading = _isUpgrading; + } +} + +contract Implementation { + function setCode(bytes memory) public pure returns (uint256) { + return 1; + } + + function setStorage(bytes32, bytes32) public pure returns (uint256) { + return 2; + } + + function setOwner(address) public pure returns (uint256) { + return 3; + } + + function getOwner() public pure returns (uint256) { + return 4; + } + + function getImplementation() public pure returns (uint256) { + return 5; + } +} + +contract L1ChugSplashProxy_Test is Test { + IL1ChugSplashProxy proxy; + address impl; + address owner = makeAddr("owner"); + address alice = makeAddr("alice"); + + function setUp() public { + proxy = IL1ChugSplashProxy( + DeployUtils.create1({ + _name: "L1ChugSplashProxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ChugSplashProxy.__constructor__, (owner))) + }) + ); + vm.prank(owner); + assertEq(proxy.getOwner(), owner); + + vm.prank(owner); + proxy.setCode(type(Implementation).runtimeCode); + + vm.prank(owner); + impl = proxy.getImplementation(); + } + + /// @notice Tests that the owner can deploy a new implementation with a given runtime code + function test_setCode_whenOwner_succeeds() public { + vm.prank(owner); + proxy.setCode(hex"604260005260206000f3"); + + vm.prank(owner); + assertNotEq(proxy.getImplementation(), impl); + } + + /// @notice Tests that when not the owner, `setCode` delegatecalls the implementation + function test_setCode_whenNotOwner_works() public view { + uint256 ret = Implementation(address(proxy)).setCode(hex"604260005260206000f3"); + assertEq(ret, 1); + } + + /// @notice Tests that when the owner deploys the same bytecode as the existing implementation, + /// it does not deploy a new implementation + function test_setCode_whenOwnerSameBytecode_works() public { + vm.prank(owner); + proxy.setCode(type(Implementation).runtimeCode); + + // does not deploy new implementation + vm.prank(owner); + assertEq(proxy.getImplementation(), impl); + } + + /// @notice Tests that when the owner calls `setCode` with insufficient gas to complete the implementation + /// contract's deployment, it reverts. + /// @dev If this solc version/settings change and modifying this proves time consuming, we can just remove it. + function test_setCode_whenOwnerAndDeployOutOfGas_reverts() public { + // The values below are best gotten by removing the gas limit parameter from the call and running the test with + // a + // verbosity of `-vvvv` then setting the value to a few thousand gas lower than the gas used by the call. + // A faster way to do this for forge coverage cases, is to comment out the optimizer and optimizer runs in + // the foundry.toml file and then run forge test. This is faster because forge test only compiles modified + // contracts unlike forge coverage. + uint256 gasLimit; + + // Because forge coverage always runs with the optimizer disabled, + // if forge coverage is run before testing this with forge test or forge snapshot, forge clean should be + // run first so that it recompiles the contracts using the foundry.toml optimizer settings. + if (vm.isContext(VmSafe.ForgeContext.Coverage)) { + gasLimit = 95_000; + } else if (vm.isContext(VmSafe.ForgeContext.Test) || vm.isContext(VmSafe.ForgeContext.Snapshot)) { + gasLimit = 65_000; + } else { + revert("SafeCall_Test: unknown context"); + } + + vm.prank(owner); + vm.expectRevert(bytes("L1ChugSplashProxy: code was not correctly deployed")); // Ran out of gas + proxy.setCode{ gas: gasLimit }( + hex"fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe" + ); + } + + /// @notice Tests that when the caller is not the owner and the implementation is not set, all calls reverts + function test_calls_whenNotOwnerNoImplementation_reverts() public { + proxy = IL1ChugSplashProxy( + DeployUtils.create1({ + _name: "L1ChugSplashProxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ChugSplashProxy.__constructor__, (owner))) + }) + ); + + vm.expectRevert(bytes("L1ChugSplashProxy: implementation is not set yet")); + Implementation(address(proxy)).setCode(hex"604260005260206000f3"); + } + + /// @notice Tests that when the caller is not the owner but the owner has marked `isUpgrading` as true, the call + /// reverts + function test_calls_whenUpgrading_reverts() public { + Owner ownerContract = new Owner(); + vm.prank(owner); + proxy.setOwner(address(ownerContract)); + + ownerContract.setIsUpgrading(true); + + vm.expectRevert(bytes("L1ChugSplashProxy: system is currently being upgraded")); + Implementation(address(proxy)).setCode(hex"604260005260206000f3"); + } + + /// @notice Tests that the owner can set storage of the proxy + function test_setStorage_whenOwner_works() public { + vm.prank(owner); + proxy.setStorage(bytes32(0), bytes32(uint256(42))); + assertEq(vm.load(address(proxy), bytes32(0)), bytes32(uint256(42))); + } + + /// @notice Tests that when not the owner, `setStorage` delegatecalls the implementation + function test_setStorage_whenNotOwner_works() public view { + uint256 ret = Implementation(address(proxy)).setStorage(bytes32(0), bytes32(uint256(42))); + assertEq(ret, 2); + assertEq(vm.load(address(proxy), bytes32(0)), bytes32(uint256(0))); + } + + /// @notice Tests that the owner can set the owner of the proxy + function test_setOwner_whenOwner_works() public { + vm.prank(owner); + proxy.setOwner(alice); + + vm.prank(alice); + assertEq(proxy.getOwner(), alice); + } + + /// @notice Tests that when not the owner, `setOwner` delegatecalls the implementation + function test_setOwner_whenNotOwner_works() public { + uint256 ret = Implementation(address(proxy)).setOwner(alice); + assertEq(ret, 3); + + vm.prank(owner); + assertEq(proxy.getOwner(), owner); + } + + /// @notice Tests that the owner can get the owner of the proxy + function test_getOwner_whenOwner_works() public { + vm.prank(owner); + assertEq(proxy.getOwner(), owner); + } + + /// @notice Tests that when not the owner, `getOwner` delegatecalls the implementation + function test_getOwner_whenNotOwner_works() public view { + uint256 ret = Implementation(address(proxy)).getOwner(); + assertEq(ret, 4); + } + + /// @notice Tests that the owner can get the implementation of the proxy + function test_getImplementation_whenOwner_works() public { + vm.prank(owner); + assertEq(proxy.getImplementation(), impl); + } + + /// @notice Tests that when not the owner, `getImplementation` delegatecalls the implementation + function test_getImplementation_whenNotOwner_works() public view { + uint256 ret = Implementation(address(proxy)).getImplementation(); + assertEq(ret, 5); + } +} diff --git a/packages/contracts-bedrock/test/legacy/LegacyMintableERC20.t.sol b/packages/contracts-bedrock/test/legacy/LegacyMintableERC20.t.sol new file mode 100644 index 0000000000000..07f43cf5a6109 --- /dev/null +++ b/packages/contracts-bedrock/test/legacy/LegacyMintableERC20.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; + +import { LegacyMintableERC20 } from "src/legacy/LegacyMintableERC20.sol"; +import { ILegacyMintableERC20 } from "interfaces/universal/ILegacyMintableERC20.sol"; + +contract LegacyMintableERC20_Test is CommonTest { + LegacyMintableERC20 legacyMintableERC20; + + function setUp() public override { + super.setUp(); + + legacyMintableERC20 = new LegacyMintableERC20(address(l2StandardBridge), address(L1Token), "_L2Token_", "_L2T_"); + } + + /// @notice Tests that the constructor sets the correct values + function test_constructor_works() public view { + assertEq(legacyMintableERC20.l2Bridge(), address(l2StandardBridge)); + assertEq(legacyMintableERC20.l1Token(), address(L1Token)); + assertEq(legacyMintableERC20.name(), "_L2Token_"); + assertEq(legacyMintableERC20.symbol(), "_L2T_"); + assertEq(legacyMintableERC20.decimals(), 18); + } + + /// @notice Tests that the contract supports the correct interfaces + function test_supportsInterface_works() public view { + assertEq(legacyMintableERC20.supportsInterface(bytes4(keccak256("supportsInterface(bytes4)"))), true); + assertEq( + legacyMintableERC20.supportsInterface( + ILegacyMintableERC20.l1Token.selector ^ ILegacyMintableERC20.mint.selector + ^ ILegacyMintableERC20.burn.selector + ), + true + ); + } + + /// @notice Tests that the mint function works when called by the bridge + function test_mint_byBridge_succeeds() public { + vm.prank(address(l2StandardBridge)); + legacyMintableERC20.mint(address(this), 1000); + assertEq(legacyMintableERC20.balanceOf(address(this)), 1000); + } + + /// @notice Tests that the mint function fails when called by an address other than the bridge + function test_mint_byNonBridge_reverts() public { + vm.expectRevert(bytes("Only L2 Bridge can mint and burn")); + legacyMintableERC20.mint(address(this), 1000); + } + + /// @notice Tests that the burn function works when called by the bridge + function test_burn_byBridge_succeeds() public { + vm.prank(address(l2StandardBridge)); + legacyMintableERC20.mint(address(this), 1000); + + vm.prank(address(l2StandardBridge)); + legacyMintableERC20.burn(address(this), 1000); + assertEq(legacyMintableERC20.balanceOf(address(this)), 0); + } + + /// @notice Tests that the burn function fails when called by an address other than the bridge + function test_burn_byNonBridge_reverts() public { + vm.expectRevert(bytes("Only L2 Bridge can mint and burn")); + legacyMintableERC20.burn(address(this), 1000); + } +} diff --git a/packages/contracts-bedrock/test/legacy/ResolvedDelegateProxy.t.sol b/packages/contracts-bedrock/test/legacy/ResolvedDelegateProxy.t.sol index 33a81bc2ca0e1..19fbdec8f7c19 100644 --- a/packages/contracts-bedrock/test/legacy/ResolvedDelegateProxy.t.sol +++ b/packages/contracts-bedrock/test/legacy/ResolvedDelegateProxy.t.sol @@ -5,45 +5,74 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; // Target contract dependencies -import { AddressManager } from "src/legacy/AddressManager.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; // Target contract -import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; +import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract ResolvedDelegateProxy_Test is Test { - AddressManager internal addressManager; + IAddressManager internal addressManager; SimpleImplementation internal impl; SimpleImplementation internal proxy; /// @dev Sets up the test suite. function setUp() public { // Set up the address manager. - addressManager = new AddressManager(); + addressManager = IAddressManager( + DeployUtils.create1({ + _name: "AddressManager", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) + }) + ); impl = new SimpleImplementation(); addressManager.setAddress("SimpleImplementation", address(impl)); // Set up the proxy. - proxy = SimpleImplementation(address(new ResolvedDelegateProxy(addressManager, "SimpleImplementation"))); + proxy = SimpleImplementation( + address( + DeployUtils.create1({ + _name: "ResolvedDelegateProxy", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IResolvedDelegateProxy.__constructor__, (addressManager, "SimpleImplementation")) + ) + }) + ) + ); } /// @dev Tests that the proxy properly bubbles up returndata when the delegatecall succeeds. function testFuzz_fallback_delegateCallFoo_succeeds(uint256 x) public { - vm.expectCall(address(impl), abi.encodeWithSelector(impl.foo.selector, x)); + vm.expectCall(address(impl), abi.encodeCall(impl.foo, (x))); assertEq(proxy.foo(x), x); } /// @dev Tests that the proxy properly bubbles up returndata when the delegatecall reverts. function test_fallback_delegateCallBar_reverts() public { vm.expectRevert("SimpleImplementation: revert"); - vm.expectCall(address(impl), abi.encodeWithSelector(impl.bar.selector)); + vm.expectCall(address(impl), abi.encodeCall(impl.bar, ())); proxy.bar(); } /// @dev Tests that the proxy fallback reverts as expected if the implementation within the /// address manager is not set. function test_fallback_addressManagerNotSet_reverts() public { - AddressManager am = new AddressManager(); - SimpleImplementation p = SimpleImplementation(address(new ResolvedDelegateProxy(am, "SimpleImplementation"))); + IAddressManager am = IAddressManager( + DeployUtils.create1({ + _name: "AddressManager", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) + }) + ); + SimpleImplementation p = SimpleImplementation( + address( + DeployUtils.create1({ + _name: "ResolvedDelegateProxy", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IResolvedDelegateProxy.__constructor__, (am, "SimpleImplementation")) + ) + }) + ) + ); vm.expectRevert("ResolvedDelegateProxy: target address must be initialized"); p.foo(0); diff --git a/packages/contracts-bedrock/test/libraries/Blueprint.t.sol b/packages/contracts-bedrock/test/libraries/Blueprint.t.sol index c94616a88e4fe..3e2a96a019128 100644 --- a/packages/contracts-bedrock/test/libraries/Blueprint.t.sol +++ b/packages/contracts-bedrock/test/libraries/Blueprint.t.sol @@ -56,7 +56,7 @@ contract Blueprint_Test is Test { // --- We start with the test cases from ERC-5202 --- // An example (and trivial!) blueprint contract with no data section, whose initcode is just the STOP instruction. - function test_ERC5202_trivialBlueprint_succeeds() public view { + function test_trivialBlueprint_erc5202_succeeds() public view { bytes memory bytecode = hex"FE710000"; Blueprint.Preamble memory preamble = blueprint.parseBlueprintPreamble(bytecode); @@ -67,7 +67,7 @@ contract Blueprint_Test is Test { // An example blueprint contract whose initcode is the trivial STOP instruction and whose data // section contains the byte 0xFF repeated seven times. - function test_ERC5202_blueprintWithDataSection_succeeds() public view { + function test_blueprintWithDataSection_erc5202_succeeds() public view { // Here, 0xFE71 is the magic header, 0x01 means version 0 + 1 length bit, 0x07 encodes the // length in bytes of the data section. These are followed by the data section, and then the // initcode. For illustration, this code with delimiters would be: @@ -82,7 +82,7 @@ contract Blueprint_Test is Test { // An example blueprint whose initcode is the trivial STOP instruction and whose data section // contains the byte 0xFF repeated 256 times. - function test_ERC5202_blueprintWithLargeDataSection_succeeds() public view { + function test_blueprintWithLargeDataSection_erc5205_succeeds() public view { // Delimited, this would be 0xFE71|02|0100|FF...FF|00 bytes memory bytecode = hex"FE71020100FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"; diff --git a/packages/contracts-bedrock/test/libraries/Encoding.t.sol b/packages/contracts-bedrock/test/libraries/Encoding.t.sol index a301fdd97b3a6..277cce328dcdd 100644 --- a/packages/contracts-bedrock/test/libraries/Encoding.t.sol +++ b/packages/contracts-bedrock/test/libraries/Encoding.t.sol @@ -71,6 +71,18 @@ contract Encoding_Test is CommonTest { assertEq(legacyEncoding, bedrockEncoding); } + /// @dev Tests that encodeCrossDomainMessage reverts if version is greater than 1. + function testFuzz_encodeCrossDomainMessage_versionGreaterThanOne_reverts(uint256 nonce) external { + // nonce >> 240 must be greater than 1 + uint256 minInvalidNonce = (uint256(type(uint240).max) + 1) * 2; + nonce = bound(nonce, minInvalidNonce, type(uint256).max); + + EncodingContract encoding = new EncodingContract(); + + vm.expectRevert(bytes("Encoding: unknown cross domain message version")); + encoding.encodeCrossDomainMessage(nonce, address(this), address(this), 1, 100, hex""); + } + /// @dev Tests deposit transaction encoding. function testDiff_encodeDepositTransaction_succeeds( address _from, @@ -94,3 +106,20 @@ contract Encoding_Test is CommonTest { assertEq(txn, _txn); } } + +contract EncodingContract { + function encodeCrossDomainMessage( + uint256 nonce, + address sender, + address target, + uint256 value, + uint256 gasLimit, + bytes memory data + ) + external + pure + returns (bytes memory) + { + return Encoding.encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data); + } +} diff --git a/packages/contracts-bedrock/test/libraries/SafeCall.t.sol b/packages/contracts-bedrock/test/libraries/SafeCall.t.sol index 5bd3fb3a4ab79..d05e952e44d9e 100644 --- a/packages/contracts-bedrock/test/libraries/SafeCall.t.sol +++ b/packages/contracts-bedrock/test/libraries/SafeCall.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; // Testing utilities import { Test } from "forge-std/Test.sol"; +import { VmSafe } from "forge-std/Vm.sol"; import { StdCheatsSafe } from "forge-std/StdCheats.sol"; // Target contract @@ -13,8 +14,6 @@ contract SafeCall_Test is Test { function assumeNot(address _addr) internal { vm.assume(_addr.balance == 0); vm.assume(_addr != address(this)); - vm.assume(uint256(uint160(_addr)) > uint256(256)); // TODO temp fix until new forge-std release with modern - // precompiles: https://github.com/foundry-rs/forge-std/pull/594 assumeAddressIsNot(_addr, StdCheatsSafe.AddressType.ForgeAddress, StdCheatsSafe.AddressType.Precompile); } @@ -54,7 +53,7 @@ contract SafeCall_Test is Test { /// @dev Tests that the `send` function with value succeeds. function testFuzz_sendWithGas_succeeds(address _from, address _to, uint64 _gas, uint256 _value) external { - vm.assume(_gas != 0); + _gas = uint64(bound(_gas, 1, type(uint64).max)); sendTest({ _from: _from, _to: _to, _gas: _gas, _value: _value }); } @@ -124,12 +123,32 @@ contract SafeCall_Test is Test { for (uint64 i = 40_000; i < 100_000; i++) { uint256 snapshot = vm.snapshot(); - // 65_907 is the exact amount of gas required to make the safe call - // successfully. - if (i < 65_907) { + // The values below are best gotten by setting the value to a high number and running the test with a + // verbosity of `-vvv` then setting the value to the value (gas arg) of the failed assertion. + // A faster way to do this for forge coverage cases, is to comment out the optimizer and optimizer runs in + // the foundry.toml file and then run forge test. This is faster because forge test only compiles modified + // contracts unlike forge coverage. + uint256 expected; + + // Because forge coverage always runs with the optimizer disabled, + // if forge coverage is run before testing this with forge test or forge snapshot, forge clean should be + // run first so that it recompiles the contracts using the foundry.toml optimizer settings. + if (vm.isContext(VmSafe.ForgeContext.Coverage)) { + // 66_290 is the exact amount of gas required to make the safe call + // successfully with the optimizer disabled (ran via forge coverage) + expected = 66_290; + } else if (vm.isContext(VmSafe.ForgeContext.Test) || vm.isContext(VmSafe.ForgeContext.Snapshot)) { + // 65_922 is the exact amount of gas required to make the safe call + // successfully with the foundry.toml optimizer settings. + expected = 65_922; + } else { + revert("SafeCall_Test: unknown context"); + } + + if (i < expected) { assertFalse(caller.makeSafeCall(i, 25_000)); } else { - vm.expectCallMinGas(address(caller), 0, 25_000, abi.encodeWithSelector(caller.setA.selector, 1)); + vm.expectCallMinGas(address(caller), 0, 25_000, abi.encodeCall(caller.setA, (1))); assertTrue(caller.makeSafeCall(i, 25_000)); } @@ -144,12 +163,32 @@ contract SafeCall_Test is Test { for (uint64 i = 15_200_000; i < 15_300_000; i++) { uint256 snapshot = vm.snapshot(); - // 15_278_606 is the exact amount of gas required to make the safe call - // successfully. - if (i < 15_278_606) { + // The values below are best gotten by setting the value to a high number and running the test with a + // verbosity of `-vvv` then setting the value to the value (gas arg) of the failed assertion. + // A faster way to do this for forge coverage cases, is to comment out the optimizer and optimizer runs in + // the foundry.toml file and then run forge test. This is faster because forge test only compiles modified + // contracts unlike forge coverage. + uint256 expected; + + // Because forge coverage always runs with the optimizer disabled, + // if forge coverage is run before testing this with forge test or forge snapshot, forge clean should be + // run first so that it recompiles the contracts using the foundry.toml optimizer settings. + if (vm.isContext(VmSafe.ForgeContext.Coverage)) { + // 15_278_989 is the exact amount of gas required to make the safe call + // successfully with the optimizer disabled (ran via forge coverage) + expected = 15_278_989; + } else if (vm.isContext(VmSafe.ForgeContext.Test) || vm.isContext(VmSafe.ForgeContext.Snapshot)) { + // 15_278_621 is the exact amount of gas required to make the safe call + // successfully with the foundry.toml optimizer settings. + expected = 15_278_621; + } else { + revert("SafeCall_Test: unknown context"); + } + + if (i < expected) { assertFalse(caller.makeSafeCall(i, 15_000_000)); } else { - vm.expectCallMinGas(address(caller), 0, 15_000_000, abi.encodeWithSelector(caller.setA.selector, 1)); + vm.expectCallMinGas(address(caller), 0, 15_000_000, abi.encodeCall(caller.setA, (1))); assertTrue(caller.makeSafeCall(i, 15_000_000)); } @@ -162,11 +201,11 @@ contract SimpleSafeCaller { uint256 public a; function makeSafeCall(uint64 gas, uint64 minGas) external returns (bool) { - return SafeCall.call(address(this), gas, 0, abi.encodeWithSelector(this.makeSafeCallMinGas.selector, minGas)); + return SafeCall.call(address(this), gas, 0, abi.encodeCall(this.makeSafeCallMinGas, (minGas))); } function makeSafeCallMinGas(uint64 minGas) external returns (bool) { - return SafeCall.callWithMinGas(address(this), minGas, 0, abi.encodeWithSelector(this.setA.selector, 1)); + return SafeCall.callWithMinGas(address(this), minGas, 0, abi.encodeCall(this.setA, (1))); } function setA(uint256 _a) external { diff --git a/packages/contracts-bedrock/test/libraries/TransientContext.t.sol b/packages/contracts-bedrock/test/libraries/TransientContext.t.sol index a7b414ad38828..20e8434b9c16c 100644 --- a/packages/contracts-bedrock/test/libraries/TransientContext.t.sol +++ b/packages/contracts-bedrock/test/libraries/TransientContext.t.sol @@ -88,7 +88,7 @@ contract TransientContextTest is Test { /// @param _slot Slot to test. /// @param _value1 Value to write to slot at call depth 0. /// @param _value2 Value to write to slot at call depth 1. - function testFuzz_setGet_twice_sameDepth_succeeds(bytes32 _slot, uint256 _value1, uint256 _value2) public { + function testFuzz_setGet_twiceSameDepth_succeeds(bytes32 _slot, uint256 _value1, uint256 _value2) public { assertEq(TransientContext.callDepth(), 0); testFuzz_set_succeeds(_slot, _value1); assertEq(TransientContext.get(_slot), _value1); @@ -102,7 +102,7 @@ contract TransientContextTest is Test { /// @param _slot Slot to test. /// @param _value1 Value to write to slot at call depth 0. /// @param _value2 Value to write to slot at call depth 1. - function testFuzz_setGet_twice_differentDepth_succeeds(bytes32 _slot, uint256 _value1, uint256 _value2) public { + function testFuzz_setGet_twiceDifferentDepth_succeeds(bytes32 _slot, uint256 _value1, uint256 _value2) public { assertEq(TransientContext.callDepth(), 0); testFuzz_set_succeeds(_slot, _value1); assertEq(TransientContext.get(_slot), _value1); diff --git a/packages/contracts-bedrock/test/libraries/trie/MerkleTrie.t.sol b/packages/contracts-bedrock/test/libraries/trie/MerkleTrie.t.sol index 7b5c7c6357b20..e6d04d971db57 100644 --- a/packages/contracts-bedrock/test/libraries/trie/MerkleTrie.t.sol +++ b/packages/contracts-bedrock/test/libraries/trie/MerkleTrie.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; import { MerkleTrie } from "src/libraries/trie/MerkleTrie.sol"; +import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; import { FFIInterface } from "test/setup/FFIInterface.sol"; import "src/libraries/rlp/RLPErrors.sol"; @@ -351,7 +352,7 @@ contract MerkleTrie_get_Test is Test { // Ambiguous revert check- all that we care is that it *does* fail. This case may // fail within different branches. - vm.expectRevert(); + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args MerkleTrie.get(key, proof, root); } @@ -372,4 +373,143 @@ contract MerkleTrie_get_Test is Test { vm.expectRevert("MerkleTrie: ran out of proof elements"); MerkleTrie.get(key, proof, root); } + + /// @notice Tests that `get` reverts if a proof node has an unknown prefix + function test_get_unknownNodePrefix_reverts(uint8 prefix) external { + // bound it to only have prefixes where the first nibble is >= 4 + prefix = uint8(bound(prefix, 0x40, 0xff)); + // if the first nibble of the prefix is odd, make it even by adding 16 + if (((prefix / 16) % 2) == 1) { + unchecked { + prefix += 16; + } + // bound it again in case it overflowed + prefix = uint8(bound(prefix, 0x40, 0xff)); + } + + MerkleTrieWrapper wrapper = new MerkleTrieWrapper(); + + bytes memory key = abi.encodePacked( + keccak256(abi.encodePacked(bytes32(0xa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c))) + ); + bytes[] memory proof = new bytes[](5); + proof[0] = + hex"f90211a085ed702d58e6a962ad0e785e5c9036e06d878fd065eb9669122447f6aee7957da05badb8cfd5a7493d928614730af6e14eabe2c93fbac93c853dde3270c446309da01de85a57c524ac56a5bd4bed0b0aa7d963e364ad930ea964d0a42631a77ded4da0fe3143892366faeb9fae1117b888263afe0f74e6c73555fee53a604bf7188431a0af2c79f0dddd15d6f62e3fa60d515c44d58444ad3915c7ca4bddb31c8f148d0ca08f37a2f9093a4aee39519f3a06fe4674cc670fbbbd7a5f4eb096310b7bc1fdc9a086bd12d2031d9714130c687e6822250fa24b3147824780bea96cf8a7406c8966a03e42538ba2da8adaa0eca4550ef62de4dabde8ca06b71ac1b276ff31b67a7655a04a439f7eb6a62c77ec921139925af3359f61d16e083076e0e425583289107d7da0c453a51991b5a4c6174ddff77c0b7d9cc86f05ffda6ff523e2365f19797c7a00a06f43b7b9a118264ab4b6c24d7d3de77f39071a298426bfc27180adfca57d590da0032e0db4dcf122d4bdb1d4ec3c5df5fabd3127bcefe412cb046b7f0d80d11c9fa0560c2b8c9466b8cb5ffd600f24ea0ed9838bfdab7870d505d4387c2468d3c498a0597996e939ff8c29c9e59dc47c111e760450a9c4fe2b065825762da2a1f32495a0e3411c9af104364230c49de5a4d0802c84df18beee9778673364e1747a875622a02a6928825356d8280f361a02285af30e73889f4e55ecb63ed85c8581e07061d680"; + proof[1] = + hex"f90211a0db246208c4cef45e9aeb9f1df1baa8572675bc453f7da538165a2bb9e6a4c416a0d26d82a9ddff901d2a1f9e18506120dec0e5b3c95549c5bff0efc355061ea73fa04f1cedbb5c7df7ee5cc3210980baf5537affb29c661c6a4eeb193bc42e7fbc74a0acea389e0cf577d0e13483d98b15c82618ac18b7cc4a479981e3e672ddd16867a0ef59a06aeea1eb5ba1313bbe1fa74ff264d84d7319ab6178213734b5b5efa9c1a08f85dc6001713d77aa4e12982dfdb28fd1c7dcc290d46f2749e8a7d67ba2a694a0f6698ff794881edc50340b75311de64ce3da5f97debcfdfd4d20de57ef3ba7eba0680071ce05e9c7915f731bac8b9673332d1d77ea1f7dadab36d9b233eea32ba4a035ad3686f436232360c2aa364c9f2aa2081318b9fb33cd1050d69ee46f791d62a03b495b3d65d9ae39680a0f835c1d1378d218f7b1fb88d2b2c6ac6ef916f09172a0a808d1e8c632d9a6cfeb3c2c123a58b5b3e1998d4bd02c5fb0f7c5d4ba1338e6a0369376e9152831135ff3a902c9740cf22951d67edd51bf0541565e379d7efc25a0cc26d7fa1c326bc14950e92d9de78e4ed8372ff9727dec34602f24057b3a9b30a0278081783022e748dc70874a72377038935c00c1f0a24bbb8cd0fc208d8b68f4a06c4e83593571b94d08cb78ece0de4920b02a650a47a16583f94c8fe35f724707a0cd7eb9d730e5138fd943200b577e7bbb827d010a50d19af2f4b19ef19658156d80"; + proof[2] = + hex"f90211a0065f58fbe63e8e3387e91047d5b7a4532e7d9de0abc47f04791aef27a140fdb5a0858beea29778551c01b0d3e542d707675856da9c3f1d065c845e55c24d77be89a0e90a410489eff6f4f8d70b0cce1fb1339117ec0f6f1db195a6cc410509a2ebaea078ba7fe504e8d01d57f6bee52c4938d779108e500b5923272441ed2964b8c45da0f0430ed9fa807e5fb6ace75f8766ea60009d8539e00006e359f5f7bc38a76596a0a98a7938db99a2d80abea6349de42bf2576c9e51cc715c85fbacab365ec16f5ba026fadc7d124a456c62ada928eaede3e80611e3e6f99041f7393f062e9e788c8ca0ca48cad1e00d22d6146173341a6060378e738be727a7265a923cf6bfd1f8b610a0f8a4aae21a78ac28e2a61f50396f9a80f6c8232fe4afa203160361c0962242baa09a1029479959fb29b4da7239393fd6ca20bc376d860324f429a51b0e0565a158a0eefb84d3943d680e176258dffe0104ac48c171a8574a811535256a2d8ba531dea062a3d709a2f70ba1970842c4f20a602291273d1f6022e7a14cde2afdcd51e795a0397e6b9b87012cd79cbd0bb7daa4cc43830a673d80b65fb88c0449140175d89ca0f8a4c73c0078cbd32961227910e3f9315bc587716062e39f66be19747ccf9b67a0ea4bdd1b187fdba273a8625f88f284994d19c38ec58651839852665717d953d9a0319ebf356f45da83c7f106f1fd3decbf15f651fad3389a0d279602cdea8ee11480"; + proof[3] = + hex"f8f1a069a092c7a950214e7e45b99012dc8ad112eab0fc94ae5ca9efbd6949068384f280a0b25c46db67ef7cf0c47bb400c31c85a26c5a204431527c964c8ecaf3d63e52cc80a01911a2a74db0d8d182447176e23f25556d1a1eaa0afad96453f2d64876ad88e480808080a04a0ca9e3bed1bc3e3c819384d19b6d5e523164a6520c4eb42e828a63ef730ae38080a03b598ed1b9269d4b05e2e75cfb54298d25437669870c919a59a147d2d256fdba80a0db2d655057c83107a73d086cfdd8fcc74739bb48c652eb0ce597178ecf96b39aa05c66ac392a761341b9c22b773ea19af311f34ef537640b9bb96842ec6ace913280"; + + proof[4] = bytes.concat( + hex"f69f", + bytes1(prefix), + hex"4dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb4" + ); + + bytes32 root; + (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); + + vm.expectRevert("MerkleTrie: received a node with an unknown prefix"); + wrapper.get(key, proof, root); + } + + /// @notice Tests that `get` reverts if a proof node is unparsable i.e list length is not 2 or 17 + function test_get_unparsableNode_reverts(uint8 listLen) external { + listLen = uint8(bound(listLen, 1, RLPReader.MAX_LIST_LENGTH)); + if (listLen == 2 || listLen == 17) { + listLen++; + } + + MerkleTrieWrapper wrapper = new MerkleTrieWrapper(); + + bytes memory key = abi.encodePacked( + keccak256(abi.encodePacked(bytes32(0xa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c))) + ); + bytes[] memory proof = new bytes[](5); + proof[0] = + hex"f90211a085ed702d58e6a962ad0e785e5c9036e06d878fd065eb9669122447f6aee7957da05badb8cfd5a7493d928614730af6e14eabe2c93fbac93c853dde3270c446309da01de85a57c524ac56a5bd4bed0b0aa7d963e364ad930ea964d0a42631a77ded4da0fe3143892366faeb9fae1117b888263afe0f74e6c73555fee53a604bf7188431a0af2c79f0dddd15d6f62e3fa60d515c44d58444ad3915c7ca4bddb31c8f148d0ca08f37a2f9093a4aee39519f3a06fe4674cc670fbbbd7a5f4eb096310b7bc1fdc9a086bd12d2031d9714130c687e6822250fa24b3147824780bea96cf8a7406c8966a03e42538ba2da8adaa0eca4550ef62de4dabde8ca06b71ac1b276ff31b67a7655a04a439f7eb6a62c77ec921139925af3359f61d16e083076e0e425583289107d7da0c453a51991b5a4c6174ddff77c0b7d9cc86f05ffda6ff523e2365f19797c7a00a06f43b7b9a118264ab4b6c24d7d3de77f39071a298426bfc27180adfca57d590da0032e0db4dcf122d4bdb1d4ec3c5df5fabd3127bcefe412cb046b7f0d80d11c9fa0560c2b8c9466b8cb5ffd600f24ea0ed9838bfdab7870d505d4387c2468d3c498a0597996e939ff8c29c9e59dc47c111e760450a9c4fe2b065825762da2a1f32495a0e3411c9af104364230c49de5a4d0802c84df18beee9778673364e1747a875622a02a6928825356d8280f361a02285af30e73889f4e55ecb63ed85c8581e07061d680"; + proof[1] = + hex"f90211a0db246208c4cef45e9aeb9f1df1baa8572675bc453f7da538165a2bb9e6a4c416a0d26d82a9ddff901d2a1f9e18506120dec0e5b3c95549c5bff0efc355061ea73fa04f1cedbb5c7df7ee5cc3210980baf5537affb29c661c6a4eeb193bc42e7fbc74a0acea389e0cf577d0e13483d98b15c82618ac18b7cc4a479981e3e672ddd16867a0ef59a06aeea1eb5ba1313bbe1fa74ff264d84d7319ab6178213734b5b5efa9c1a08f85dc6001713d77aa4e12982dfdb28fd1c7dcc290d46f2749e8a7d67ba2a694a0f6698ff794881edc50340b75311de64ce3da5f97debcfdfd4d20de57ef3ba7eba0680071ce05e9c7915f731bac8b9673332d1d77ea1f7dadab36d9b233eea32ba4a035ad3686f436232360c2aa364c9f2aa2081318b9fb33cd1050d69ee46f791d62a03b495b3d65d9ae39680a0f835c1d1378d218f7b1fb88d2b2c6ac6ef916f09172a0a808d1e8c632d9a6cfeb3c2c123a58b5b3e1998d4bd02c5fb0f7c5d4ba1338e6a0369376e9152831135ff3a902c9740cf22951d67edd51bf0541565e379d7efc25a0cc26d7fa1c326bc14950e92d9de78e4ed8372ff9727dec34602f24057b3a9b30a0278081783022e748dc70874a72377038935c00c1f0a24bbb8cd0fc208d8b68f4a06c4e83593571b94d08cb78ece0de4920b02a650a47a16583f94c8fe35f724707a0cd7eb9d730e5138fd943200b577e7bbb827d010a50d19af2f4b19ef19658156d80"; + proof[2] = + hex"f90211a0065f58fbe63e8e3387e91047d5b7a4532e7d9de0abc47f04791aef27a140fdb5a0858beea29778551c01b0d3e542d707675856da9c3f1d065c845e55c24d77be89a0e90a410489eff6f4f8d70b0cce1fb1339117ec0f6f1db195a6cc410509a2ebaea078ba7fe504e8d01d57f6bee52c4938d779108e500b5923272441ed2964b8c45da0f0430ed9fa807e5fb6ace75f8766ea60009d8539e00006e359f5f7bc38a76596a0a98a7938db99a2d80abea6349de42bf2576c9e51cc715c85fbacab365ec16f5ba026fadc7d124a456c62ada928eaede3e80611e3e6f99041f7393f062e9e788c8ca0ca48cad1e00d22d6146173341a6060378e738be727a7265a923cf6bfd1f8b610a0f8a4aae21a78ac28e2a61f50396f9a80f6c8232fe4afa203160361c0962242baa09a1029479959fb29b4da7239393fd6ca20bc376d860324f429a51b0e0565a158a0eefb84d3943d680e176258dffe0104ac48c171a8574a811535256a2d8ba531dea062a3d709a2f70ba1970842c4f20a602291273d1f6022e7a14cde2afdcd51e795a0397e6b9b87012cd79cbd0bb7daa4cc43830a673d80b65fb88c0449140175d89ca0f8a4c73c0078cbd32961227910e3f9315bc587716062e39f66be19747ccf9b67a0ea4bdd1b187fdba273a8625f88f284994d19c38ec58651839852665717d953d9a0319ebf356f45da83c7f106f1fd3decbf15f651fad3389a0d279602cdea8ee11480"; + proof[3] = + hex"f8f1a069a092c7a950214e7e45b99012dc8ad112eab0fc94ae5ca9efbd6949068384f280a0b25c46db67ef7cf0c47bb400c31c85a26c5a204431527c964c8ecaf3d63e52cc80a01911a2a74db0d8d182447176e23f25556d1a1eaa0afad96453f2d64876ad88e480808080a04a0ca9e3bed1bc3e3c819384d19b6d5e523164a6520c4eb42e828a63ef730ae38080a03b598ed1b9269d4b05e2e75cfb54298d25437669870c919a59a147d2d256fdba80a0db2d655057c83107a73d086cfdd8fcc74739bb48c652eb0ce597178ecf96b39aa05c66ac392a761341b9c22b773ea19af311f34ef537640b9bb96842ec6ace913280"; + proof[4] = + hex"f69f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb4"; // Correct + // leaf node + + bytes32 root = keccak256(proof[0]); + + // Should not revert + wrapper.get(key, proof, root); + + if (listLen > 3) { + // Node with list > 3 + proof[4] = + hex"f8379f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb480"; + for (uint256 i; i < listLen - 3; i++) { + proof[4] = bytes.concat(proof[4], hex"80"); + } + proof[4][1] = bytes1(uint8(proof[4][1]) + (listLen - 3)); + // rehash all proof elements and insert it into the proof element above it + (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); + + vm.expectRevert("MerkleTrie: received an unparseable node"); + wrapper.get(key, proof, root); + } else if (listLen == 1) { + // Node with list of 1 + proof[4] = hex"e09f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f1"; + // rehash all proof elements and insert it into the proof element above it + (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); + + vm.expectRevert("MerkleTrie: received an unparseable node"); + wrapper.get(key, proof, root); + } else if (listLen == 3) { + // Node with list of 3 + proof[4] = + hex"f79f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb480"; + // rehash all proof elements and insert it into the proof element above it + (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); + + vm.expectRevert("MerkleTrie: received an unparseable node"); + wrapper.get(key, proof, root); + } + } + + function rehashOtherElements(bytes memory _proof4) + private + pure + returns (bytes memory proof0_, bytes memory proof1_, bytes memory proof2_, bytes memory proof3_, bytes32 root_) + { + // rehash all proof elements and insert it into the proof element above it + proof3_ = bytes.concat( + hex"f8f1a069a092c7a950214e7e45b99012dc8ad112eab0fc94ae5ca9efbd6949068384f280a0b25c46db67ef7cf0c47bb400c31c85a26c5a204431527c964c8ecaf3d63e52cc80a0", + keccak256(_proof4), + hex"80808080a04a0ca9e3bed1bc3e3c819384d19b6d5e523164a6520c4eb42e828a63ef730ae38080a03b598ed1b9269d4b05e2e75cfb54298d25437669870c919a59a147d2d256fdba80a0db2d655057c83107a73d086cfdd8fcc74739bb48c652eb0ce597178ecf96b39aa05c66ac392a761341b9c22b773ea19af311f34ef537640b9bb96842ec6ace913280" + ); + proof2_ = bytes.concat( + hex"f90211a0065f58fbe63e8e3387e91047d5b7a4532e7d9de0abc47f04791aef27a140fdb5a0858beea29778551c01b0d3e542d707675856da9c3f1d065c845e55c24d77be89a0e90a410489eff6f4f8d70b0cce1fb1339117ec0f6f1db195a6cc410509a2ebaea078ba7fe504e8d01d57f6bee52c4938d779108e500b5923272441ed2964b8c45da0f0430ed9fa807e5fb6ace75f8766ea60009d8539e00006e359f5f7bc38a76596a0a98a7938db99a2d80abea6349de42bf2576c9e51cc715c85fbacab365ec16f5ba0", + keccak256(proof3_), + hex"a0ca48cad1e00d22d6146173341a6060378e738be727a7265a923cf6bfd1f8b610a0f8a4aae21a78ac28e2a61f50396f9a80f6c8232fe4afa203160361c0962242baa09a1029479959fb29b4da7239393fd6ca20bc376d860324f429a51b0e0565a158a0eefb84d3943d680e176258dffe0104ac48c171a8574a811535256a2d8ba531dea062a3d709a2f70ba1970842c4f20a602291273d1f6022e7a14cde2afdcd51e795a0397e6b9b87012cd79cbd0bb7daa4cc43830a673d80b65fb88c0449140175d89ca0f8a4c73c0078cbd32961227910e3f9315bc587716062e39f66be19747ccf9b67a0ea4bdd1b187fdba273a8625f88f284994d19c38ec58651839852665717d953d9a0319ebf356f45da83c7f106f1fd3decbf15f651fad3389a0d279602cdea8ee11480" + ); + proof1_ = bytes.concat( + hex"f90211a0db246208c4cef45e9aeb9f1df1baa8572675bc453f7da538165a2bb9e6a4c416a0d26d82a9ddff901d2a1f9e18506120dec0e5b3c95549c5bff0efc355061ea73fa04f1cedbb5c7df7ee5cc3210980baf5537affb29c661c6a4eeb193bc42e7fbc74a0acea389e0cf577d0e13483d98b15c82618ac18b7cc4a479981e3e672ddd16867a0ef59a06aeea1eb5ba1313bbe1fa74ff264d84d7319ab6178213734b5b5efa9c1a08f85dc6001713d77aa4e12982dfdb28fd1c7dcc290d46f2749e8a7d67ba2a694a0f6698ff794881edc50340b75311de64ce3da5f97debcfdfd4d20de57ef3ba7eba0680071ce05e9c7915f731bac8b9673332d1d77ea1f7dadab36d9b233eea32ba4a035ad3686f436232360c2aa364c9f2aa2081318b9fb33cd1050d69ee46f791d62a03b495b3d65d9ae39680a0f835c1d1378d218f7b1fb88d2b2c6ac6ef916f09172a0a808d1e8c632d9a6cfeb3c2c123a58b5b3e1998d4bd02c5fb0f7c5d4ba1338e6a0369376e9152831135ff3a902c9740cf22951d67edd51bf0541565e379d7efc25a0", + keccak256(proof2_), + hex"a0278081783022e748dc70874a72377038935c00c1f0a24bbb8cd0fc208d8b68f4a06c4e83593571b94d08cb78ece0de4920b02a650a47a16583f94c8fe35f724707a0cd7eb9d730e5138fd943200b577e7bbb827d010a50d19af2f4b19ef19658156d80" + ); + proof0_ = bytes.concat( + hex"f90211a085ed702d58e6a962ad0e785e5c9036e06d878fd065eb9669122447f6aee7957da05badb8cfd5a7493d928614730af6e14eabe2c93fbac93c853dde3270c446309da0", + keccak256(proof1_), + hex"a0fe3143892366faeb9fae1117b888263afe0f74e6c73555fee53a604bf7188431a0af2c79f0dddd15d6f62e3fa60d515c44d58444ad3915c7ca4bddb31c8f148d0ca08f37a2f9093a4aee39519f3a06fe4674cc670fbbbd7a5f4eb096310b7bc1fdc9a086bd12d2031d9714130c687e6822250fa24b3147824780bea96cf8a7406c8966a03e42538ba2da8adaa0eca4550ef62de4dabde8ca06b71ac1b276ff31b67a7655a04a439f7eb6a62c77ec921139925af3359f61d16e083076e0e425583289107d7da0c453a51991b5a4c6174ddff77c0b7d9cc86f05ffda6ff523e2365f19797c7a00a06f43b7b9a118264ab4b6c24d7d3de77f39071a298426bfc27180adfca57d590da0032e0db4dcf122d4bdb1d4ec3c5df5fabd3127bcefe412cb046b7f0d80d11c9fa0560c2b8c9466b8cb5ffd600f24ea0ed9838bfdab7870d505d4387c2468d3c498a0597996e939ff8c29c9e59dc47c111e760450a9c4fe2b065825762da2a1f32495a0e3411c9af104364230c49de5a4d0802c84df18beee9778673364e1747a875622a02a6928825356d8280f361a02285af30e73889f4e55ecb63ed85c8581e07061d680" + ); + root_ = keccak256(proof0_); + } +} + +contract MerkleTrieWrapper { + function get(bytes memory key, bytes[] memory proof, bytes32 root) external pure returns (bytes memory) { + return MerkleTrie.get(key, proof, root); + } } diff --git a/packages/contracts-bedrock/test/mocks/AlphabetVM.sol b/packages/contracts-bedrock/test/mocks/AlphabetVM.sol index 6ecf74e22868a..ae024e5b2a127 100644 --- a/packages/contracts-bedrock/test/mocks/AlphabetVM.sol +++ b/packages/contracts-bedrock/test/mocks/AlphabetVM.sol @@ -6,7 +6,7 @@ import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol"; import "src/dispute/lib/Types.sol"; // Interfaces -import { IBigStepper, IPreimageOracle } from "src/dispute/interfaces/IBigStepper.sol"; +import { IBigStepper, IPreimageOracle } from "interfaces/dispute/IBigStepper.sol"; /// @title AlphabetVM /// @dev A mock VM for the purpose of testing the dispute game infrastructure. Note that this only works diff --git a/packages/contracts-bedrock/test/mocks/Callers.sol b/packages/contracts-bedrock/test/mocks/Callers.sol index aa7a2dc8b2289..b8dd70df15e14 100644 --- a/packages/contracts-bedrock/test/mocks/Callers.sol +++ b/packages/contracts-bedrock/test/mocks/Callers.sol @@ -84,7 +84,7 @@ contract ConfigurableCaller { /// @dev Any call will revert contract Reverter { function doRevert() public pure { - revert("Reverter reverted"); + revert("Reverter: Reverter reverted"); } fallback() external { diff --git a/packages/contracts-bedrock/test/mocks/OptimistInviterHelper.sol b/packages/contracts-bedrock/test/mocks/OptimistInviterHelper.sol deleted file mode 100644 index ebc2289f9c10d..0000000000000 --- a/packages/contracts-bedrock/test/mocks/OptimistInviterHelper.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -/// @notice Simple helper contract that helps with testing flow and signature for -/// OptimistInviter contract. Made this a separate contract instead of including -/// in OptimistInviter.t.sol for reusability. -contract OptimistInviterHelper { - /// @notice EIP712 typehash for the ClaimableInvite type. - bytes32 public constant CLAIMABLE_INVITE_TYPEHASH = keccak256("ClaimableInvite(address issuer,bytes32 nonce)"); - - /// @notice EIP712 typehash for the EIP712Domain type that is included as part of the signature. - bytes32 public constant EIP712_DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - /// @notice Address of OptimistInviter contract we are testing. - OptimistInviter public optimistInviter; - - /// @notice OptimistInviter contract name. Used to construct the EIP-712 domain. - string public name; - - /// @notice Keeps track of current nonce to generate new nonces for each invite. - uint256 public currentNonce; - - constructor(OptimistInviter _optimistInviter, string memory _name) { - optimistInviter = _optimistInviter; - name = _name; - } - - /// @notice Returns the hash of the struct ClaimableInvite. - /// @param _claimableInvite ClaimableInvite struct to hash. - /// @return EIP-712 typed struct hash. - function getClaimableInviteStructHash(OptimistInviter.ClaimableInvite memory _claimableInvite) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(CLAIMABLE_INVITE_TYPEHASH, _claimableInvite.issuer, _claimableInvite.nonce)); - } - - /// @notice Returns a bytes32 nonce that should change everytime. In practice, people should use - /// pseudorandom nonces. - /// @return Nonce that should be used as part of ClaimableInvite. - function consumeNonce() public returns (bytes32) { - return bytes32(keccak256(abi.encode(currentNonce++))); - } - - /// @notice Returns a ClaimableInvite with the issuer and current nonce. - /// @param _issuer Issuer to include in the ClaimableInvite. - /// @return ClaimableInvite that can be hashed & signed. - function getClaimableInviteWithNewNonce(address _issuer) public returns (OptimistInviter.ClaimableInvite memory) { - return OptimistInviter.ClaimableInvite(_issuer, consumeNonce()); - } - - /// @notice Computes the EIP712 digest with default correct parameters. - /// @param _claimableInvite ClaimableInvite struct to hash. - /// @return EIP-712 compatible digest. - function getDigest(OptimistInviter.ClaimableInvite calldata _claimableInvite) public view returns (bytes32) { - return getDigestWithEIP712Domain( - _claimableInvite, - bytes(name), - bytes(optimistInviter.EIP712_VERSION()), - block.chainid, - address(optimistInviter) - ); - } - - /// @notice Computes the EIP712 digest with the given domain parameters. - /// Used for testing that different domain parameters fail. - /// @param _claimableInvite ClaimableInvite struct to hash. - /// @param _name Contract name to use in the EIP712 domain. - /// @param _version Contract version to use in the EIP712 domain. - /// @param _chainid Chain ID to use in the EIP712 domain. - /// @param _verifyingContract Address to use in the EIP712 domain. - /// @return EIP-712 compatible digest. - function getDigestWithEIP712Domain( - OptimistInviter.ClaimableInvite calldata _claimableInvite, - bytes memory _name, - bytes memory _version, - uint256 _chainid, - address _verifyingContract - ) - public - pure - returns (bytes32) - { - bytes32 domainSeparator = keccak256( - abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256(_name), keccak256(_version), _chainid, _verifyingContract) - ); - return ECDSA.toTypedDataHash(domainSeparator, getClaimableInviteStructHash(_claimableInvite)); - } -} diff --git a/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol new file mode 100644 index 0000000000000..f0ef5f882f988 --- /dev/null +++ b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; + +/// @title SuperchainERC20Implementation Mock contract +/// @notice Mock contract just to create tests over an implementation of the SuperchainERC20 abstract contract. +contract MockSuperchainERC20Implementation is SuperchainERC20 { + function name() public pure override returns (string memory) { + return "SuperchainERC20"; + } + + function symbol() public pure override returns (string memory) { + return "SCE"; + } +} diff --git a/packages/contracts-bedrock/test/mocks/TestERC1271Wallet.sol b/packages/contracts-bedrock/test/mocks/TestERC1271Wallet.sol index c04ed608b7d70..6e2ef47f67e61 100644 --- a/packages/contracts-bedrock/test/mocks/TestERC1271Wallet.sol +++ b/packages/contracts-bedrock/test/mocks/TestERC1271Wallet.sol @@ -13,7 +13,15 @@ contract TestERC1271Wallet is Ownable, IERC1271 { transferOwnership(originalOwner); } - function isValidSignature(bytes32 hash, bytes memory signature) public view override returns (bytes4 magicValue) { - return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0); + function isValidSignature( + bytes32 _hash, + bytes memory _signature + ) + public + view + override + returns (bytes4 magicValue_) + { + return ECDSA.recover(_hash, _signature) == owner() ? this.isValidSignature.selector : bytes4(0); } } diff --git a/packages/contracts-bedrock/test/mocks/block.json b/packages/contracts-bedrock/test/mocks/block.json deleted file mode 100644 index 7de13ed6135a6..0000000000000 --- a/packages/contracts-bedrock/test/mocks/block.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "hash": "0xfd3c5e25a80f54a53c58bd3ad8c076dc1c0cdbd44ec2164d2d2b8cc50481cb78", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "miner": "0x0000000000000000000000000000000000000000", - "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "gasLimit": "0x1c9c380", - "extraData": "0x", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0x654caabb", - "difficulty": "0x0", - "totalDifficulty": "0x0", - "sealFields": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000" - ], - "uncles": [], - "transactions": [], - "size": "0x202", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "nonce": "0x0000000000000000", - "baseFeePerGas": "0x3b9aca00" -} diff --git a/packages/contracts-bedrock/test/opcm/DeployAltDA.t.sol b/packages/contracts-bedrock/test/opcm/DeployAltDA.t.sol new file mode 100644 index 0000000000000..a23708e427ac4 --- /dev/null +++ b/packages/contracts-bedrock/test/opcm/DeployAltDA.t.sol @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Test } from "forge-std/Test.sol"; + +import { DeployAltDAInput, DeployAltDAOutput, DeployAltDA } from "scripts/deploy/DeployAltDA.s.sol"; +import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +contract DeployAltDAInput_Test is Test { + DeployAltDAInput dai; + + // Define defaults + bytes32 salt = bytes32(uint256(1)); + address proxyAdminAddr = makeAddr("proxyAdmin"); + address challengeContractOwner = makeAddr("challengeContractOwner"); + uint256 challengeWindow = 100; + uint256 resolveWindow = 200; + uint256 bondSize = 1 ether; + uint256 resolverRefundPercentage = 10; + + function setUp() public { + dai = new DeployAltDAInput(); + } + + function test_set_succeeds() public { + dai.set(dai.salt.selector, salt); + dai.set(dai.proxyAdmin.selector, proxyAdminAddr); + dai.set(dai.challengeContractOwner.selector, challengeContractOwner); + dai.set(dai.challengeWindow.selector, challengeWindow); + dai.set(dai.resolveWindow.selector, resolveWindow); + dai.set(dai.bondSize.selector, bondSize); + dai.set(dai.resolverRefundPercentage.selector, resolverRefundPercentage); + + // Compare the default inputs to the getter methods + assertEq(salt, dai.salt(), "100"); + assertEq(proxyAdminAddr, address(dai.proxyAdmin()), "200"); + assertEq(challengeContractOwner, dai.challengeContractOwner(), "300"); + assertEq(challengeWindow, dai.challengeWindow(), "400"); + assertEq(resolveWindow, dai.resolveWindow(), "500"); + assertEq(bondSize, dai.bondSize(), "600"); + assertEq(resolverRefundPercentage, dai.resolverRefundPercentage(), "700"); + } + + function test_getters_whenNotSet_reverts() public { + bytes memory expectedErr = "DeployAltDAInput: "; + + vm.expectRevert(abi.encodePacked(expectedErr, "salt not set")); + dai.salt(); + + vm.expectRevert(abi.encodePacked(expectedErr, "proxyAdmin not set")); + dai.proxyAdmin(); + + vm.expectRevert(abi.encodePacked(expectedErr, "challengeContractOwner not set")); + dai.challengeContractOwner(); + + vm.expectRevert(abi.encodePacked(expectedErr, "challengeWindow not set")); + dai.challengeWindow(); + + vm.expectRevert(abi.encodePacked(expectedErr, "resolveWindow not set")); + dai.resolveWindow(); + + vm.expectRevert(abi.encodePacked(expectedErr, "bondSize not set")); + dai.bondSize(); + + vm.expectRevert(abi.encodePacked(expectedErr, "resolverRefundPercentage not set")); + dai.resolverRefundPercentage(); + } + + function test_set_zeroAddress_reverts() public { + vm.expectRevert("DeployAltDAInput: cannot set zero address"); + dai.set(dai.proxyAdmin.selector, address(0)); + + vm.expectRevert("DeployAltDAInput: cannot set zero address"); + dai.set(dai.challengeContractOwner.selector, address(0)); + } + + function test_set_unknownSelector_reverts() public { + bytes4 unknownSelector = bytes4(keccak256("unknown()")); + + vm.expectRevert("DeployAltDAInput: unknown selector"); + dai.set(unknownSelector, bytes32(0)); + + vm.expectRevert("DeployAltDAInput: unknown selector"); + dai.set(unknownSelector, address(1)); + + vm.expectRevert("DeployAltDAInput: unknown selector"); + dai.set(unknownSelector, uint256(1)); + } +} + +contract DeployAltDAOutput_Test is Test { + DeployAltDAOutput dao; + + // Store contract references to avoid stack too deep + IDataAvailabilityChallenge internal dataAvailabilityChallengeImpl; + + function setUp() public { + dao = new DeployAltDAOutput(); + dataAvailabilityChallengeImpl = IDataAvailabilityChallenge(payable(makeAddr("dataAvailabilityChallengeImpl"))); + } + + function test_set_succeeds() public { + // Build the implementation with some bytecode + vm.etch(address(dataAvailabilityChallengeImpl), hex"01"); + + // Build proxy with implementation + (IProxy dataAvailabilityChallengeProxy) = + DeployUtils.buildERC1967ProxyWithImpl("dataAvailabilityChallengeProxy"); + + // Set the addresses + dao.set(dao.dataAvailabilityChallengeProxy.selector, address(dataAvailabilityChallengeProxy)); + dao.set(dao.dataAvailabilityChallengeImpl.selector, address(dataAvailabilityChallengeImpl)); + + // Verify the addresses were set correctly + assertEq(address(dataAvailabilityChallengeProxy), address(dao.dataAvailabilityChallengeProxy()), "100"); + assertEq(address(dataAvailabilityChallengeImpl), address(dao.dataAvailabilityChallengeImpl()), "200"); + } + + function test_getters_whenNotSet_reverts() public { + vm.expectRevert("DeployUtils: zero address"); + dao.dataAvailabilityChallengeProxy(); + + vm.expectRevert("DeployUtils: zero address"); + dao.dataAvailabilityChallengeImpl(); + } + + function test_getters_whenAddrHasNoCode_reverts() public { + address emptyAddr = makeAddr("emptyAddr"); + bytes memory expectedErr = bytes(string.concat("DeployUtils: no code at ", vm.toString(emptyAddr))); + + dao.set(dao.dataAvailabilityChallengeProxy.selector, emptyAddr); + vm.expectRevert(expectedErr); + dao.dataAvailabilityChallengeProxy(); + + dao.set(dao.dataAvailabilityChallengeImpl.selector, emptyAddr); + vm.expectRevert(expectedErr); + dao.dataAvailabilityChallengeImpl(); + } + + function test_set_zeroAddress_reverts() public { + vm.expectRevert("DeployAltDAOutput: cannot set zero address"); + dao.set(dao.dataAvailabilityChallengeProxy.selector, address(0)); + + vm.expectRevert("DeployAltDAOutput: cannot set zero address"); + dao.set(dao.dataAvailabilityChallengeImpl.selector, address(0)); + } + + function test_set_unknownSelector_reverts() public { + bytes4 unknownSelector = bytes4(keccak256("unknown()")); + vm.expectRevert("DeployAltDAOutput: unknown selector"); + dao.set(unknownSelector, address(1)); + } +} + +contract DeployAltDA_Test is Test { + DeployAltDA deployer; + DeployAltDAInput dai; + DeployAltDAOutput dao; + + // Define defaults + bytes32 salt = bytes32(uint256(1)); + IProxyAdmin proxyAdmin; + address challengeContractOwner = makeAddr("challengeContractOwner"); + uint256 challengeWindow = 100; + uint256 resolveWindow = 200; + uint256 bondSize = 1 ether; + uint256 resolverRefundPercentage = 10; + + function setUp() public { + // Deploy the main contract and get input/output contracts + deployer = new DeployAltDA(); + (dai, dao) = _setupIOContracts(); + + // Setup proxyAdmin + proxyAdmin = IProxyAdmin( + DeployUtils.create1({ + _name: "ProxyAdmin", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (msg.sender))) + }) + ); + + // Set the default values + dai.set(dai.salt.selector, salt); + dai.set(dai.proxyAdmin.selector, address(proxyAdmin)); + dai.set(dai.challengeContractOwner.selector, challengeContractOwner); + dai.set(dai.challengeWindow.selector, challengeWindow); + dai.set(dai.resolveWindow.selector, resolveWindow); + dai.set(dai.bondSize.selector, bondSize); + dai.set(dai.resolverRefundPercentage.selector, resolverRefundPercentage); + } + + function _setupIOContracts() internal returns (DeployAltDAInput, DeployAltDAOutput) { + DeployAltDAInput _dai = new DeployAltDAInput(); + DeployAltDAOutput _dao = new DeployAltDAOutput(); + return (_dai, _dao); + } + + function test_run_succeeds() public { + deployer.run(dai, dao); + + // Verify everything is set up correctly + IDataAvailabilityChallenge dac = dao.dataAvailabilityChallengeProxy(); + assertTrue(address(dac).code.length > 0, "100"); + assertTrue(address(dao.dataAvailabilityChallengeImpl()).code.length > 0, "200"); + + // Check all initialization parameters + assertEq(dac.owner(), challengeContractOwner, "300"); + assertEq(dac.challengeWindow(), challengeWindow, "400"); + assertEq(dac.resolveWindow(), resolveWindow, "500"); + assertEq(dac.bondSize(), bondSize, "600"); + assertEq(dac.resolverRefundPercentage(), resolverRefundPercentage, "700"); + // Make sure the proxy admin is set correctly. + vm.prank(address(0)); + assertEq(IProxy(payable(address(dac))).admin(), address(proxyAdmin), "800"); + } + + function test_checkOutput_whenNotInitialized_reverts() public { + vm.expectRevert("DeployUtils: zero address"); + deployer.checkOutput(dai, dao); + } + + function test_checkOutput_whenProxyNotInitialized_reverts() public { + // Deploy but don't initialize + deployer.deployDataAvailabilityChallengeProxy(dai, dao); + deployer.deployDataAvailabilityChallengeImpl(dai, dao); + + vm.expectRevert("DeployUtils: zero address"); + deployer.checkOutput(dai, dao); + } + + function testFuzz_run_withDifferentParameters_works( + uint256 _challengeWindow, + uint256 _resolveWindow, + uint256 _bondSize, + uint256 _resolverRefundPercentage + ) + public + { + // Bound the values to reasonable ranges + _challengeWindow = bound(_challengeWindow, 1, 365 days); + _resolveWindow = bound(_resolveWindow, 1, 365 days); + _bondSize = bound(_bondSize, 0.1 ether, 100 ether); + _resolverRefundPercentage = bound(_resolverRefundPercentage, 1, 100); + + // Set the new values + dai.set(dai.salt.selector, salt); + dai.set(dai.proxyAdmin.selector, address(proxyAdmin)); + dai.set(dai.challengeWindow.selector, _challengeWindow); + dai.set(dai.resolveWindow.selector, _resolveWindow); + dai.set(dai.bondSize.selector, _bondSize); + dai.set(dai.resolverRefundPercentage.selector, _resolverRefundPercentage); + + // Run deployment + deployer.run(dai, dao); + + // Verify values + IDataAvailabilityChallenge dac = dao.dataAvailabilityChallengeProxy(); + assertEq(dac.challengeWindow(), _challengeWindow, "100"); + assertEq(dac.resolveWindow(), _resolveWindow, "200"); + assertEq(dac.bondSize(), _bondSize, "300"); + assertEq(dac.resolverRefundPercentage(), _resolverRefundPercentage, "400"); + } +} diff --git a/packages/contracts-bedrock/test/opcm/DeployAuthSystem.t.sol b/packages/contracts-bedrock/test/opcm/DeployAuthSystem.t.sol index 19985ef490b9d..7126ce25efaff 100644 --- a/packages/contracts-bedrock/test/opcm/DeployAuthSystem.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployAuthSystem.t.sol @@ -5,7 +5,7 @@ import { Test, stdStorage, StdStorage } from "forge-std/Test.sol"; import { stdToml } from "forge-std/StdToml.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; -import { DeployAuthSystemInput, DeployAuthSystem, DeployAuthSystemOutput } from "scripts/DeployAuthSystem.s.sol"; +import { DeployAuthSystemInput, DeployAuthSystem, DeployAuthSystemOutput } from "scripts/deploy/DeployAuthSystem.s.sol"; contract DeployAuthSystemInput_Test is Test { DeployAuthSystemInput dasi; @@ -30,7 +30,7 @@ contract DeployAuthSystemInput_Test is Test { } } - function test_getters_whenNotSet_revert() public { + function test_getters_whenNotSet_reverts() public { vm.expectRevert("DeployAuthSystemInput: threshold not set"); dasi.threshold(); @@ -38,7 +38,7 @@ contract DeployAuthSystemInput_Test is Test { dasi.owners(); } - function test_setters_ownerAlreadySet_revert() public { + function test_setters_ownerAlreadySet_reverts() public { dasi.set(dasi.owners.selector, owners); vm.expectRevert("DeployAuthSystemInput: owners already set"); @@ -135,7 +135,7 @@ contract DeployAuthSystem_Test is Test { daso.checkOutput(); } - function test_run_NullInput_reverts() public { + function test_run_nullInput_reverts() public { dasi.set(dasi.owners.selector, defaultOwners); dasi.set(dasi.threshold.selector, defaultThreshold); diff --git a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol index 490d764698cac..584aa59a9c2b6 100644 --- a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol @@ -4,29 +4,29 @@ pragma solidity 0.8.15; import { Test, stdStorage, StdStorage } from "forge-std/Test.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; -import { IMIPS } from "src/cannon/interfaces/IMIPS.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; +import { IMIPS } from "interfaces/cannon/IMIPS.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; -import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; import { DeployImplementationsInput, DeployImplementations, DeployImplementationsInterop, DeployImplementationsOutput -} from "scripts/DeployImplementations.s.sol"; +} from "scripts/deploy/DeployImplementations.s.sol"; contract DeployImplementationsInput_Test is Test { DeployImplementationsInput dii; @@ -44,7 +44,7 @@ contract DeployImplementationsInput_Test is Test { dii = new DeployImplementationsInput(); } - function test_getters_whenNotSet_revert() public { + function test_getters_whenNotSet_reverts() public { vm.expectRevert("DeployImplementationsInput: not set"); dii.withdrawalDelaySeconds(); @@ -61,7 +61,7 @@ contract DeployImplementationsInput_Test is Test { dii.disputeGameFinalityDelaySeconds(); vm.expectRevert("DeployImplementationsInput: not set"); - dii.release(); + dii.l1ContractsRelease(); vm.expectRevert("DeployImplementationsInput: not set"); dii.superchainConfigProxy(); @@ -69,23 +69,9 @@ contract DeployImplementationsInput_Test is Test { vm.expectRevert("DeployImplementationsInput: not set"); dii.protocolVersionsProxy(); - vm.expectRevert("DeployImplementationsInput: not set"); - dii.opcmProxyOwner(); - vm.expectRevert("DeployImplementationsInput: not set"); dii.standardVersionsToml(); } - - function test_opcmProxyOwner_whenNotSet_reverts() public { - vm.expectRevert("DeployImplementationsInput: not set"); - dii.opcmProxyOwner(); - } - - function test_opcmProxyOwner_succeeds() public { - dii.set(dii.opcmProxyOwner.selector, address(msg.sender)); - address opcmProxyOwner = dii.opcmProxyOwner(); - assertEq(address(msg.sender), address(opcmProxyOwner), "100"); - } } contract DeployImplementationsOutput_Test is Test { @@ -96,17 +82,7 @@ contract DeployImplementationsOutput_Test is Test { } function test_set_succeeds() public { - IProxy proxy = IProxy( - DeployUtils.create1({ - _name: "Proxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (address(0)))) - }) - ); - address opcmImpl = address(makeAddr("opcmImpl")); - vm.prank(address(0)); - proxy.upgradeTo(opcmImpl); - - OPContractsManager opcmProxy = OPContractsManager(address(proxy)); + OPContractsManager opcm = OPContractsManager(address(makeAddr("opcm"))); IOptimismPortal2 optimismPortalImpl = IOptimismPortal2(payable(makeAddr("optimismPortalImpl"))); IDelayedWETH delayedWETHImpl = IDelayedWETH(payable(makeAddr("delayedWETHImpl"))); IPreimageOracle preimageOracleSingleton = IPreimageOracle(makeAddr("preimageOracleSingleton")); @@ -120,8 +96,7 @@ contract DeployImplementationsOutput_Test is Test { IOptimismMintableERC20Factory(makeAddr("optimismMintableERC20FactoryImpl")); IDisputeGameFactory disputeGameFactoryImpl = IDisputeGameFactory(makeAddr("disputeGameFactoryImpl")); - vm.etch(address(opcmProxy), address(opcmProxy).code); - vm.etch(address(opcmImpl), hex"01"); + vm.etch(address(opcm), hex"01"); vm.etch(address(optimismPortalImpl), hex"01"); vm.etch(address(delayedWETHImpl), hex"01"); vm.etch(address(preimageOracleSingleton), hex"01"); @@ -132,7 +107,7 @@ contract DeployImplementationsOutput_Test is Test { vm.etch(address(l1StandardBridgeImpl), hex"01"); vm.etch(address(optimismMintableERC20FactoryImpl), hex"01"); vm.etch(address(disputeGameFactoryImpl), hex"01"); - dio.set(dio.opcmProxy.selector, address(opcmProxy)); + dio.set(dio.opcm.selector, address(opcm)); dio.set(dio.optimismPortalImpl.selector, address(optimismPortalImpl)); dio.set(dio.delayedWETHImpl.selector, address(delayedWETHImpl)); dio.set(dio.preimageOracleSingleton.selector, address(preimageOracleSingleton)); @@ -144,7 +119,7 @@ contract DeployImplementationsOutput_Test is Test { dio.set(dio.optimismMintableERC20FactoryImpl.selector, address(optimismMintableERC20FactoryImpl)); dio.set(dio.disputeGameFactoryImpl.selector, address(disputeGameFactoryImpl)); - assertEq(address(opcmProxy), address(dio.opcmProxy()), "50"); + assertEq(address(opcm), address(dio.opcm()), "50"); assertEq(address(optimismPortalImpl), address(dio.optimismPortalImpl()), "100"); assertEq(address(delayedWETHImpl), address(dio.delayedWETHImpl()), "200"); assertEq(address(preimageOracleSingleton), address(dio.preimageOracleSingleton()), "300"); @@ -157,7 +132,7 @@ contract DeployImplementationsOutput_Test is Test { assertEq(address(disputeGameFactoryImpl), address(dio.disputeGameFactoryImpl()), "950"); } - function test_getters_whenNotSet_revert() public { + function test_getters_whenNotSet_reverts() public { bytes memory expectedErr = "DeployUtils: zero address"; vm.expectRevert(expectedErr); @@ -273,7 +248,7 @@ contract DeployImplementations_Test is Test { function test_deployImplementation_succeeds() public { string memory deployContractsRelease = "dev-release"; - dii.set(dii.release.selector, deployContractsRelease); + dii.set(dii.l1ContractsRelease.selector, deployContractsRelease); deployImplementations.deploySystemConfigImpl(dii, dio); assertTrue(address(0) != address(dio.systemConfigImpl())); } @@ -282,7 +257,7 @@ contract DeployImplementations_Test is Test { // All hardcoded addresses below are taken from the superchain-registry config: // https://github.com/ethereum-optimism/superchain-registry/blob/be65d22f8128cf0c4e5b4e1f677daf86843426bf/validation/standard/standard-versions.toml#L11 string memory testRelease = "op-contracts/v1.6.0"; - dii.set(dii.release.selector, testRelease); + dii.set(dii.l1ContractsRelease.selector, testRelease); deployImplementations.deploySystemConfigImpl(dii, dio); address srSystemConfigImpl = address(0xF56D96B2535B932656d3c04Ebf51baBff241D886); @@ -335,71 +310,6 @@ contract DeployImplementations_Test is Test { assertEq(srDisputeGameFactoryImpl, address(dio.disputeGameFactoryImpl())); } - function test_deployAtNonExistentRelease_reverts() public { - string memory unknownRelease = "op-contracts/v0.0.0"; - dii.set(dii.release.selector, unknownRelease); - - bytes memory expectedErr = - bytes(string.concat("DeployImplementations: failed to deploy release ", unknownRelease)); - - vm.expectRevert(expectedErr); - deployImplementations.deploySystemConfigImpl(dii, dio); - - vm.expectRevert(expectedErr); - deployImplementations.deployL1CrossDomainMessengerImpl(dii, dio); - - vm.expectRevert(expectedErr); - deployImplementations.deployL1ERC721BridgeImpl(dii, dio); - - vm.expectRevert(expectedErr); - deployImplementations.deployL1StandardBridgeImpl(dii, dio); - - vm.expectRevert(expectedErr); - deployImplementations.deployOptimismMintableERC20FactoryImpl(dii, dio); - - // TODO: Uncomment the code below when OPContractsManager is deployed based on release. Superchain-registry - // doesn't contain OPContractsManager yet. - // dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); - // dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); - // vm.etch(address(superchainConfigProxy), hex"01"); - // vm.etch(address(protocolVersionsProxy), hex"01"); - // vm.expectRevert(expectedErr); - // deployImplementations.deployOPContractsManagerImpl(dii, dio); - - dii.set(dii.proofMaturityDelaySeconds.selector, 1); - dii.set(dii.disputeGameFinalityDelaySeconds.selector, 2); - vm.expectRevert(expectedErr); - deployImplementations.deployOptimismPortalImpl(dii, dio); - - dii.set(dii.withdrawalDelaySeconds.selector, 1); - vm.expectRevert(expectedErr); - deployImplementations.deployDelayedWETHImpl(dii, dio); - - dii.set(dii.minProposalSizeBytes.selector, 1); - dii.set(dii.challengePeriodSeconds.selector, 2); - vm.expectRevert(expectedErr); - deployImplementations.deployPreimageOracleSingleton(dii, dio); - - address preImageOracleSingleton = makeAddr("preImageOracleSingleton"); - vm.etch(address(preImageOracleSingleton), hex"01"); - dio.set(dio.preimageOracleSingleton.selector, preImageOracleSingleton); - vm.expectRevert(expectedErr); - deployImplementations.deployMipsSingleton(dii, dio); - - vm.expectRevert(expectedErr); // fault proof contracts don't exist at this release - deployImplementations.deployDisputeGameFactoryImpl(dii, dio); - } - - function test_noContractExistsAtRelease_reverts() public { - string memory unknownRelease = "op-contracts/v1.3.0"; - dii.set(dii.release.selector, unknownRelease); - bytes memory expectedErr = - bytes(string.concat("DeployImplementations: failed to deploy release ", unknownRelease)); - - vm.expectRevert(expectedErr); // fault proof contracts don't exist at this release - deployImplementations.deployDisputeGameFactoryImpl(dii, dio); - } - function testFuzz_run_memory_succeeds(bytes32 _seed) public { withdrawalDelaySeconds = uint256(hash(_seed, 0)); minProposalSizeBytes = uint256(hash(_seed, 1)); @@ -409,7 +319,7 @@ contract DeployImplementations_Test is Test { string memory release = string(bytes.concat(hash(_seed, 5))); protocolVersionsProxy = IProtocolVersions(address(uint160(uint256(hash(_seed, 7))))); - // Must configure the ProxyAdmin contract which is used to upgrade the OPCM's proxy contract. + // Must configure the ProxyAdmin contract. IProxyAdmin superchainProxyAdmin = IProxyAdmin( DeployUtils.create1({ _name: "ProxyAdmin", @@ -439,10 +349,9 @@ contract DeployImplementations_Test is Test { dii.set(dii.proofMaturityDelaySeconds.selector, proofMaturityDelaySeconds); dii.set(dii.disputeGameFinalityDelaySeconds.selector, disputeGameFinalityDelaySeconds); dii.set(dii.mipsVersion.selector, 1); - dii.set(dii.release.selector, release); + dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); - dii.set(dii.opcmProxyOwner.selector, msg.sender); deployImplementations.run(dii, dio); @@ -453,10 +362,9 @@ contract DeployImplementations_Test is Test { assertEq(proofMaturityDelaySeconds, dii.proofMaturityDelaySeconds(), "400"); assertEq(disputeGameFinalityDelaySeconds, dii.disputeGameFinalityDelaySeconds(), "500"); assertEq(1, dii.mipsVersion(), "512"); - assertEq(release, dii.release(), "525"); + assertEq(release, dii.l1ContractsRelease(), "525"); assertEq(address(superchainConfigProxy), address(dii.superchainConfigProxy()), "550"); assertEq(address(protocolVersionsProxy), address(dii.protocolVersionsProxy()), "575"); - assertEq(msg.sender, dii.opcmProxyOwner(), "580"); // Architecture assertions. assertEq(address(dio.mipsSingleton().oracle()), address(dio.preimageOracleSingleton()), "600"); @@ -475,7 +383,7 @@ contract DeployImplementations_Test is Test { dii.set(dii.disputeGameFinalityDelaySeconds.selector, disputeGameFinalityDelaySeconds); dii.set(dii.mipsVersion.selector, 1); string memory release = "dev-release"; - dii.set(dii.release.selector, release); + dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); diff --git a/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol new file mode 100644 index 0000000000000..959b9c7031f91 --- /dev/null +++ b/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Test } from "forge-std/Test.sol"; +import { DeployOPCM, DeployOPCMInput, DeployOPCMOutput } from "scripts/deploy/DeployOPCM.s.sol"; +import { OPContractsManager } from "src/L1/OPContractsManager.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; + +contract DeployOPCMInput_Test is Test { + DeployOPCMInput dii; + string release = "1.0.0"; + + function setUp() public { + dii = new DeployOPCMInput(); + } + + function test_getters_whenNotSet_reverts() public { + vm.expectRevert("DeployOPCMInput: not set"); + dii.superchainConfig(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.protocolVersions(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.l1ContractsRelease(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.addressManagerBlueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.proxyBlueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.proxyAdminBlueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.l1ChugSplashProxyBlueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.resolvedDelegateProxyBlueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.anchorStateRegistryBlueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.permissionedDisputeGame1Blueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.permissionedDisputeGame2Blueprint(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.l1ERC721BridgeImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.optimismPortalImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.systemConfigImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.optimismMintableERC20FactoryImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.l1CrossDomainMessengerImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.l1StandardBridgeImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.disputeGameFactoryImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.delayedWETHImpl(); + + vm.expectRevert("DeployOPCMInput: not set"); + dii.mipsImpl(); + } + + // Below setter tests are split into two parts to avoid stack too deep errors + + function test_set_part1_succeeds() public { + ISuperchainConfig superchainConfig = ISuperchainConfig(makeAddr("superchainConfig")); + IProtocolVersions protocolVersions = IProtocolVersions(makeAddr("protocolVersions")); + address addressManagerBlueprint = makeAddr("addressManagerBlueprint"); + address proxyBlueprint = makeAddr("proxyBlueprint"); + address proxyAdminBlueprint = makeAddr("proxyAdminBlueprint"); + address l1ChugSplashProxyBlueprint = makeAddr("l1ChugSplashProxyBlueprint"); + address resolvedDelegateProxyBlueprint = makeAddr("resolvedDelegateProxyBlueprint"); + address anchorStateRegistryBlueprint = makeAddr("anchorStateRegistryBlueprint"); + address permissionedDisputeGame1Blueprint = makeAddr("permissionedDisputeGame1Blueprint"); + address permissionedDisputeGame2Blueprint = makeAddr("permissionedDisputeGame2Blueprint"); + + dii.set(dii.superchainConfig.selector, address(superchainConfig)); + dii.set(dii.protocolVersions.selector, address(protocolVersions)); + dii.set(dii.l1ContractsRelease.selector, release); + dii.set(dii.addressManagerBlueprint.selector, addressManagerBlueprint); + dii.set(dii.proxyBlueprint.selector, proxyBlueprint); + dii.set(dii.proxyAdminBlueprint.selector, proxyAdminBlueprint); + dii.set(dii.l1ChugSplashProxyBlueprint.selector, l1ChugSplashProxyBlueprint); + dii.set(dii.resolvedDelegateProxyBlueprint.selector, resolvedDelegateProxyBlueprint); + dii.set(dii.anchorStateRegistryBlueprint.selector, anchorStateRegistryBlueprint); + dii.set(dii.permissionedDisputeGame1Blueprint.selector, permissionedDisputeGame1Blueprint); + dii.set(dii.permissionedDisputeGame2Blueprint.selector, permissionedDisputeGame2Blueprint); + + assertEq(address(dii.superchainConfig()), address(superchainConfig), "50"); + assertEq(address(dii.protocolVersions()), address(protocolVersions), "100"); + assertEq(dii.l1ContractsRelease(), release, "150"); + assertEq(dii.addressManagerBlueprint(), addressManagerBlueprint, "200"); + assertEq(dii.proxyBlueprint(), proxyBlueprint, "250"); + assertEq(dii.proxyAdminBlueprint(), proxyAdminBlueprint, "300"); + assertEq(dii.l1ChugSplashProxyBlueprint(), l1ChugSplashProxyBlueprint, "350"); + assertEq(dii.resolvedDelegateProxyBlueprint(), resolvedDelegateProxyBlueprint, "400"); + assertEq(dii.anchorStateRegistryBlueprint(), anchorStateRegistryBlueprint, "450"); + assertEq(dii.permissionedDisputeGame1Blueprint(), permissionedDisputeGame1Blueprint, "500"); + assertEq(dii.permissionedDisputeGame2Blueprint(), permissionedDisputeGame2Blueprint, "550"); + } + + function test_set_part2_succeeds() public { + address l1ERC721BridgeImpl = makeAddr("l1ERC721BridgeImpl"); + address optimismPortalImpl = makeAddr("optimismPortalImpl"); + address systemConfigImpl = makeAddr("systemConfigImpl"); + address optimismMintableERC20FactoryImpl = makeAddr("optimismMintableERC20FactoryImpl"); + address l1CrossDomainMessengerImpl = makeAddr("l1CrossDomainMessengerImpl"); + address l1StandardBridgeImpl = makeAddr("l1StandardBridgeImpl"); + address disputeGameFactoryImpl = makeAddr("disputeGameFactoryImpl"); + address delayedWETHImpl = makeAddr("delayedWETHImpl"); + address mipsImpl = makeAddr("mipsImpl"); + + dii.set(dii.l1ERC721BridgeImpl.selector, l1ERC721BridgeImpl); + dii.set(dii.optimismPortalImpl.selector, optimismPortalImpl); + dii.set(dii.systemConfigImpl.selector, systemConfigImpl); + dii.set(dii.optimismMintableERC20FactoryImpl.selector, optimismMintableERC20FactoryImpl); + dii.set(dii.l1CrossDomainMessengerImpl.selector, l1CrossDomainMessengerImpl); + dii.set(dii.l1StandardBridgeImpl.selector, l1StandardBridgeImpl); + dii.set(dii.disputeGameFactoryImpl.selector, disputeGameFactoryImpl); + dii.set(dii.delayedWETHImpl.selector, delayedWETHImpl); + dii.set(dii.mipsImpl.selector, mipsImpl); + + assertEq(dii.l1ERC721BridgeImpl(), l1ERC721BridgeImpl, "600"); + assertEq(dii.optimismPortalImpl(), optimismPortalImpl, "650"); + assertEq(dii.systemConfigImpl(), systemConfigImpl, "700"); + assertEq(dii.optimismMintableERC20FactoryImpl(), optimismMintableERC20FactoryImpl, "750"); + assertEq(dii.l1CrossDomainMessengerImpl(), l1CrossDomainMessengerImpl, "800"); + assertEq(dii.l1StandardBridgeImpl(), l1StandardBridgeImpl, "850"); + assertEq(dii.disputeGameFactoryImpl(), disputeGameFactoryImpl, "900"); + assertEq(dii.delayedWETHImpl(), delayedWETHImpl, "950"); + assertEq(dii.mipsImpl(), mipsImpl, "1000"); + } + + function test_set_withZeroAddress_reverts() public { + vm.expectRevert("DeployOPCMInput: cannot set zero address"); + dii.set(dii.superchainConfig.selector, address(0)); + } + + function test_set_withEmptyString_reverts() public { + vm.expectRevert("DeployOPCMInput: cannot set empty string"); + dii.set(dii.l1ContractsRelease.selector, ""); + } + + function test_set_withInvalidSelector_reverts() public { + vm.expectRevert("DeployOPCMInput: unknown selector"); + dii.set(bytes4(0xdeadbeef), address(1)); + } + + function test_set_withInvalidStringSelector_reverts() public { + vm.expectRevert("DeployOPCMInput: unknown selector"); + dii.set(bytes4(0xdeadbeef), "test"); + } +} + +contract DeployOPCMOutput_Test is Test { + DeployOPCMOutput doo; + + function setUp() public { + doo = new DeployOPCMOutput(); + } + + function test_getters_whenNotSet_reverts() public { + vm.expectRevert("DeployOPCMOutput: not set"); + doo.opcm(); + } + + function test_set_succeeds() public { + OPContractsManager opcm = OPContractsManager(makeAddr("opcm")); + vm.etch(address(opcm), hex"01"); + + doo.set(doo.opcm.selector, address(opcm)); + + assertEq(address(doo.opcm()), address(opcm), "50"); + } + + function test_set_withZeroAddress_reverts() public { + vm.expectRevert("DeployOPCMOutput: cannot set zero address"); + doo.set(doo.opcm.selector, address(0)); + } + + function test_set_withInvalidSelector_reverts() public { + vm.expectRevert("DeployOPCMOutput: unknown selector"); + doo.set(bytes4(0xdeadbeef), makeAddr("test")); + } +} + +contract DeployOPCMTest is Test { + DeployOPCM deployOPCM; + DeployOPCMInput doi; + DeployOPCMOutput doo; + + ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfigProxy")); + IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy")); + + function setUp() public virtual { + deployOPCM = new DeployOPCM(); + (doi, doo) = deployOPCM.etchIOContracts(); + } + + function test_run_succeeds() public { + doi.set(doi.superchainConfig.selector, address(superchainConfigProxy)); + doi.set(doi.protocolVersions.selector, address(protocolVersionsProxy)); + doi.set(doi.l1ContractsRelease.selector, "1.0.0"); + + // Set and etch blueprints + doi.set(doi.addressManagerBlueprint.selector, makeAddr("addressManagerBlueprint")); + doi.set(doi.proxyBlueprint.selector, makeAddr("proxyBlueprint")); + doi.set(doi.proxyAdminBlueprint.selector, makeAddr("proxyAdminBlueprint")); + doi.set(doi.l1ChugSplashProxyBlueprint.selector, makeAddr("l1ChugSplashProxyBlueprint")); + doi.set(doi.resolvedDelegateProxyBlueprint.selector, makeAddr("resolvedDelegateProxyBlueprint")); + doi.set(doi.anchorStateRegistryBlueprint.selector, makeAddr("anchorStateRegistryBlueprint")); + doi.set(doi.permissionedDisputeGame1Blueprint.selector, makeAddr("permissionedDisputeGame1Blueprint")); + doi.set(doi.permissionedDisputeGame2Blueprint.selector, makeAddr("permissionedDisputeGame2Blueprint")); + + // Set and etch implementations + doi.set(doi.l1ERC721BridgeImpl.selector, makeAddr("l1ERC721BridgeImpl")); + doi.set(doi.optimismPortalImpl.selector, makeAddr("optimismPortalImpl")); + doi.set(doi.systemConfigImpl.selector, makeAddr("systemConfigImpl")); + doi.set(doi.optimismMintableERC20FactoryImpl.selector, makeAddr("optimismMintableERC20FactoryImpl")); + doi.set(doi.l1CrossDomainMessengerImpl.selector, makeAddr("l1CrossDomainMessengerImpl")); + doi.set(doi.l1StandardBridgeImpl.selector, makeAddr("l1StandardBridgeImpl")); + doi.set(doi.disputeGameFactoryImpl.selector, makeAddr("disputeGameFactoryImpl")); + doi.set(doi.delayedWETHImpl.selector, makeAddr("delayedWETHImpl")); + doi.set(doi.mipsImpl.selector, makeAddr("mipsImpl")); + + // Etch all addresses with dummy bytecode + vm.etch(address(doi.superchainConfig()), hex"01"); + vm.etch(address(doi.protocolVersions()), hex"01"); + + vm.etch(doi.addressManagerBlueprint(), hex"01"); + vm.etch(doi.proxyBlueprint(), hex"01"); + vm.etch(doi.proxyAdminBlueprint(), hex"01"); + vm.etch(doi.l1ChugSplashProxyBlueprint(), hex"01"); + vm.etch(doi.resolvedDelegateProxyBlueprint(), hex"01"); + vm.etch(doi.anchorStateRegistryBlueprint(), hex"01"); + vm.etch(doi.permissionedDisputeGame1Blueprint(), hex"01"); + vm.etch(doi.permissionedDisputeGame2Blueprint(), hex"01"); + + vm.etch(doi.l1ERC721BridgeImpl(), hex"01"); + vm.etch(doi.optimismPortalImpl(), hex"01"); + vm.etch(doi.systemConfigImpl(), hex"01"); + vm.etch(doi.optimismMintableERC20FactoryImpl(), hex"01"); + vm.etch(doi.l1CrossDomainMessengerImpl(), hex"01"); + vm.etch(doi.l1StandardBridgeImpl(), hex"01"); + vm.etch(doi.disputeGameFactoryImpl(), hex"01"); + vm.etch(doi.delayedWETHImpl(), hex"01"); + vm.etch(doi.mipsImpl(), hex"01"); + + deployOPCM.run(doi, doo); + + assertNotEq(address(doo.opcm()), address(0)); + + // sanity check to ensure that the OPCM is validated + deployOPCM.assertValidOpcm(doi, doo); + } +} diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index 13460f35c6324..5ac899579d65e 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -3,29 +3,29 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; -import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/DeploySuperchain.s.sol"; +import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/deploy/DeploySuperchain.s.sol"; import { DeployImplementationsInput, DeployImplementations, DeployImplementationsInterop, DeployImplementationsOutput -} from "scripts/DeployImplementations.s.sol"; -import { DeployOPChainInput, DeployOPChain, DeployOPChainOutput } from "scripts/DeployOPChain.s.sol"; +} from "scripts/deploy/DeployImplementations.s.sol"; +import { DeployOPChainInput, DeployOPChain, DeployOPChainOutput } from "scripts/deploy/DeployOPChain.s.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; -import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IPermissionedDisputeGame } from "src/dispute/interfaces/IPermissionedDisputeGame.sol"; -import { IL1ChugSplashProxy } from "src/legacy/interfaces/IL1ChugSplashProxy.sol"; -import { IResolvedDelegateProxy } from "src/legacy/interfaces/IResolvedDelegateProxy.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; +import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; +import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; -import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; import { Claim, Duration, GameType, GameTypes, Hash, OutputRoot } from "src/dispute/lib/Types.sol"; @@ -39,10 +39,10 @@ contract DeployOPChainInput_Test is Test { address unsafeBlockSigner = makeAddr("unsafeBlockSigner"); address proposer = makeAddr("proposer"); address challenger = makeAddr("challenger"); + address opcm = makeAddr("opcm"); uint32 basefeeScalar = 100; uint32 blobBaseFeeScalar = 200; uint256 l2ChainId = 300; - OPContractsManager opcm = OPContractsManager(makeAddr("opcm")); string saltMixer = "saltMixer"; function setUp() public { @@ -59,9 +59,9 @@ contract DeployOPChainInput_Test is Test { doi.set(doi.basefeeScalar.selector, basefeeScalar); doi.set(doi.blobBaseFeeScalar.selector, blobBaseFeeScalar); doi.set(doi.l2ChainId.selector, l2ChainId); - - (IProxy opcmProxy) = DeployUtils.buildERC1967ProxyWithImpl("opcmProxy"); - doi.set(doi.opcmProxy.selector, address(opcmProxy)); + doi.set(doi.allowCustomDisputeParameters.selector, true); + doi.set(doi.opcm.selector, opcm); + vm.etch(opcm, hex"01"); // Compare the default inputs to the getter methods. assertEq(opChainProxyAdminOwner, doi.opChainProxyAdminOwner(), "200"); @@ -73,10 +73,11 @@ contract DeployOPChainInput_Test is Test { assertEq(basefeeScalar, doi.basefeeScalar(), "800"); assertEq(blobBaseFeeScalar, doi.blobBaseFeeScalar(), "900"); assertEq(l2ChainId, doi.l2ChainId(), "1000"); - assertEq(address(opcmProxy), address(doi.opcmProxy()), "1100"); + assertEq(opcm, address(doi.opcm()), "1100"); + assertEq(true, doi.allowCustomDisputeParameters(), "1200"); } - function test_getters_whenNotSet_revert() public { + function test_getters_whenNotSet_reverts() public { bytes memory expectedErr = "DeployOPChainInput: not set"; vm.expectRevert(expectedErr); @@ -180,7 +181,7 @@ contract DeployOPChainOutput_Test is Test { // "1600"); } - function test_getters_whenNotSet_revert() public { + function test_getters_whenNotSet_reverts() public { bytes memory expectedErr = "DeployUtils: zero address"; vm.expectRevert(expectedErr); @@ -338,7 +339,7 @@ contract DeployOPChain_TestBase is Test { IAnchorStateRegistry.StartingAnchorRoot[] startingAnchorRoots; OPContractsManager opcm = OPContractsManager(address(0)); string saltMixer = "defaultSaltMixer"; - uint64 gasLimit = 30_000_000; + uint64 gasLimit = 60_000_000; // Configurable dispute game parameters. uint32 disputeGameType = GameType.unwrap(GameTypes.PERMISSIONED_CANNON); bytes32 disputeAbsolutePrestate = hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"; @@ -394,7 +395,7 @@ contract DeployOPChain_TestBase is Test { dii.set(dii.proofMaturityDelaySeconds.selector, proofMaturityDelaySeconds); dii.set(dii.disputeGameFinalityDelaySeconds.selector, disputeGameFinalityDelaySeconds); dii.set(dii.mipsVersion.selector, 1); - dii.set(dii.release.selector, release); + dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); // End users of the DeployImplementations contract will need to set the `standardVersionsToml`. @@ -402,7 +403,7 @@ contract DeployOPChain_TestBase is Test { string.concat(vm.projectRoot(), "/test/fixtures/standard-versions.toml"); string memory standardVersionsToml = vm.readFile(standardVersionsTomlPath); dii.set(dii.standardVersionsToml.selector, standardVersionsToml); - dii.set(dii.opcmProxyOwner.selector, address(1)); + deployImplementations.run(dii, dio); // Deploy DeployOpChain, but defer populating the input values to the test suites inheriting this contract. @@ -410,7 +411,7 @@ contract DeployOPChain_TestBase is Test { (doi, doo) = deployOPChain.etchIOContracts(); // Set the OPContractsManager input for DeployOPChain. - opcm = dio.opcmProxy(); + opcm = dio.opcm(); } // See the function of the same name in the `DeployImplementations_Test` contract of @@ -425,7 +426,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { return keccak256(abi.encode(_seed, _i)); } - function testFuzz_run_memory_succeed(bytes32 _seed) public { + function testFuzz_run_memory_succeeds(bytes32 _seed) public { opChainProxyAdminOwner = address(uint160(uint256(hash(_seed, 0)))); systemConfigOwner = address(uint160(uint256(hash(_seed, 1)))); batcher = address(uint160(uint256(hash(_seed, 2)))); @@ -464,7 +465,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { doi.set(doi.basefeeScalar.selector, basefeeScalar); doi.set(doi.blobBaseFeeScalar.selector, blobBaseFeeScalar); doi.set(doi.l2ChainId.selector, l2ChainId); - doi.set(doi.opcmProxy.selector, address(opcm)); // Not fuzzed since it must be an actual instance. + doi.set(doi.opcm.selector, address(opcm)); doi.set(doi.saltMixer.selector, saltMixer); doi.set(doi.gasLimit.selector, gasLimit); doi.set(doi.disputeGameType.selector, disputeGameType); @@ -531,6 +532,42 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { assertEq(address(doo.opChainProxyAdmin().addressManager()), address(doo.addressManager()), "3700"); assertEq(address(doo.opChainProxyAdmin().owner()), opChainProxyAdminOwner, "3800"); } + + function test_customDisputeGame_customDisabled_reverts() public { + setDOI(); + doi.set(doi.disputeSplitDepth.selector, disputeSplitDepth + 1); + vm.expectRevert("DPG-90"); + deployOPChain.run(doi, doo); + } + + function test_customDisputeGame_customEnabled_succeeds() public { + setDOI(); + doi.set(doi.allowCustomDisputeParameters.selector, true); + doi.set(doi.disputeSplitDepth.selector, disputeSplitDepth + 1); + deployOPChain.run(doi, doo); + assertEq(doo.permissionedDisputeGame().splitDepth(), disputeSplitDepth + 1); + } + + function setDOI() internal { + doi.set(doi.opChainProxyAdminOwner.selector, opChainProxyAdminOwner); + doi.set(doi.systemConfigOwner.selector, systemConfigOwner); + doi.set(doi.batcher.selector, batcher); + doi.set(doi.unsafeBlockSigner.selector, unsafeBlockSigner); + doi.set(doi.proposer.selector, proposer); + doi.set(doi.challenger.selector, challenger); + doi.set(doi.basefeeScalar.selector, basefeeScalar); + doi.set(doi.blobBaseFeeScalar.selector, blobBaseFeeScalar); + doi.set(doi.l2ChainId.selector, l2ChainId); + doi.set(doi.opcm.selector, address(opcm)); + doi.set(doi.saltMixer.selector, saltMixer); + doi.set(doi.gasLimit.selector, gasLimit); + doi.set(doi.disputeGameType.selector, disputeGameType); + doi.set(doi.disputeAbsolutePrestate.selector, disputeAbsolutePrestate); + doi.set(doi.disputeMaxGameDepth.selector, disputeMaxGameDepth); + doi.set(doi.disputeSplitDepth.selector, disputeSplitDepth); + doi.set(doi.disputeClockExtension.selector, disputeClockExtension); + doi.set(doi.disputeMaxClockDuration.selector, disputeMaxClockDuration); + } } contract DeployOPChain_Test_Interop is DeployOPChain_Test { diff --git a/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol b/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol index 8641772a74d90..93c3c0c9344a9 100644 --- a/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol @@ -7,8 +7,8 @@ import { stdToml } from "forge-std/StdToml.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { Proxy } from "src/universal/Proxy.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; -import { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; -import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/DeploySuperchain.s.sol"; +import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; +import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/deploy/DeploySuperchain.s.sol"; contract DeploySuperchainInput_Test is Test { DeploySuperchainInput dsi; @@ -24,7 +24,7 @@ contract DeploySuperchainInput_Test is Test { dsi = new DeploySuperchainInput(); } - function test_getters_whenNotSet_revert() public { + function test_getters_whenNotSet_reverts() public { vm.expectRevert("DeploySuperchainInput: superchainProxyAdminOwner not set"); dsi.superchainProxyAdminOwner(); @@ -83,7 +83,7 @@ contract DeploySuperchainOutput_Test is Test { assertEq(address(protocolVersionsProxy), address(dso.protocolVersionsProxy()), "500"); } - function test_getters_whenNotSet_revert() public { + function test_getters_whenNotSet_reverts() public { vm.expectRevert("DeployUtils: zero address"); dso.superchainConfigImpl(); @@ -194,7 +194,7 @@ contract DeploySuperchain_Test is Test { dso.checkOutput(dsi); } - function test_run_NullInput_reverts() public { + function test_run_nullInput_reverts() public { // Set default values for all inputs. dsi.set(dsi.superchainProxyAdminOwner.selector, defaultProxyAdminOwner); dsi.set(dsi.protocolVersionsOwner.selector, defaultProtocolVersionsOwner); diff --git a/packages/contracts-bedrock/test/periphery/Transactor.t.sol b/packages/contracts-bedrock/test/periphery/Transactor.t.sol index bfee0ddc3adf2..8d183c8d20b73 100644 --- a/packages/contracts-bedrock/test/periphery/Transactor.t.sol +++ b/packages/contracts-bedrock/test/periphery/Transactor.t.sol @@ -41,7 +41,7 @@ contract TransactorTest is Transactor_Initializer { /// @notice Tests CALL, should do a call to target function test_call_succeeds() external { // Initialize call data - bytes memory data = abi.encodeWithSelector(callRecorded.record.selector); + bytes memory data = abi.encodeCall(CallRecorder.record, ()); // Run CALL vm.prank(alice); vm.expectCall(address(callRecorded), 200_000 wei, data); @@ -51,7 +51,7 @@ contract TransactorTest is Transactor_Initializer { /// @notice It should revert if called by non-owner function test_call_unauthorized_reverts() external { // Initialize call data - bytes memory data = abi.encodeWithSelector(callRecorded.record.selector); + bytes memory data = abi.encodeCall(CallRecorder.record, ()); // Run CALL vm.prank(bob); vm.expectRevert("UNAUTHORIZED"); @@ -61,7 +61,7 @@ contract TransactorTest is Transactor_Initializer { /// @notice Deletate call succeeds. function test_delegateCall_succeeds() external { // Initialize call data - bytes memory data = abi.encodeWithSelector(reverter.doRevert.selector); + bytes memory data = abi.encodeCall(Reverter.doRevert, ()); // Run CALL vm.prank(alice); vm.expectCall(address(reverter), data); @@ -71,7 +71,7 @@ contract TransactorTest is Transactor_Initializer { /// @notice It should revert if called by non-owner function test_delegateCall_unauthorized_reverts() external { // Initialize call data - bytes memory data = abi.encodeWithSelector(reverter.doRevert.selector); + bytes memory data = abi.encodeCall(Reverter.doRevert, ()); // Run CALL vm.prank(bob); vm.expectRevert("UNAUTHORIZED"); diff --git a/packages/contracts-bedrock/test/periphery/drippie/Drippie.t.sol b/packages/contracts-bedrock/test/periphery/drippie/Drippie.t.sol index 66c2a8d69b192..c5791e06868bf 100644 --- a/packages/contracts-bedrock/test/periphery/drippie/Drippie.t.sol +++ b/packages/contracts-bedrock/test/periphery/drippie/Drippie.t.sol @@ -16,10 +16,6 @@ import { SimpleStorage } from "test/mocks/SimpleStorage.sol"; contract TestDrippie is Drippie { constructor(address owner) Drippie(owner) { } - function dripStatus(string memory name) external view returns (Drippie.DripStatus) { - return drips[name].status; - } - function dripStateLast(string memory name) external view returns (uint256) { return drips[name].last; } @@ -131,12 +127,12 @@ contract Drippie_Test is Test { vm.prank(drippie.owner()); drippie.create(dripName, cfg); - Drippie.DripStatus status = drippie.dripStatus(dripName); + Drippie.DripStatus status = drippie.getDripStatus(dripName); Drippie.DripConfig memory config = drippie.dripConfig(dripName); assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED)); - assertEq(config.interval, cfg.interval); + assertEq(drippie.getDripInterval(dripName), cfg.interval); assertEq(config.reentrant, cfg.reentrant); assertEq(address(config.dripcheck), address(cfg.dripcheck)); assertEq(config.checkparams, cfg.checkparams); @@ -186,7 +182,7 @@ contract Drippie_Test is Test { address owner = drippie.owner(); { - Drippie.DripStatus status = drippie.dripStatus(dripName); + Drippie.DripStatus status = drippie.getDripStatus(dripName); assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED)); } @@ -198,7 +194,7 @@ contract Drippie_Test is Test { drippie.status(dripName, Drippie.DripStatus.ACTIVE); { - Drippie.DripStatus status = drippie.dripStatus(dripName); + Drippie.DripStatus status = drippie.getDripStatus(dripName); assertEq(uint256(status), uint256(Drippie.DripStatus.ACTIVE)); } @@ -210,7 +206,7 @@ contract Drippie_Test is Test { drippie.status(dripName, Drippie.DripStatus.PAUSED); { - Drippie.DripStatus status = drippie.dripStatus(dripName); + Drippie.DripStatus status = drippie.getDripStatus(dripName); assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED)); } } @@ -252,7 +248,7 @@ contract Drippie_Test is Test { drippie.status(dripName, Drippie.DripStatus.ARCHIVED); - Drippie.DripStatus status = drippie.dripStatus(dripName); + Drippie.DripStatus status = drippie.getDripStatus(dripName); assertEq(uint256(status), uint256(Drippie.DripStatus.ARCHIVED)); } @@ -353,7 +349,7 @@ contract Drippie_Test is Test { // Add in an action cfg.actions[0] = Drippie.DripAction({ target: payable(address(simpleStorage)), - data: abi.encodeWithSelector(SimpleStorage.set.selector, key, value), + data: abi.encodeCall(SimpleStorage.set, (key, value)), value: 0 }); @@ -365,7 +361,7 @@ contract Drippie_Test is Test { vm.prank(drippie.owner()); drippie.status(dripName, Drippie.DripStatus.ACTIVE); - vm.expectCall(address(simpleStorage), 0, abi.encodeWithSelector(SimpleStorage.set.selector, key, value)); + vm.expectCall(address(simpleStorage), 0, abi.encodeCall(SimpleStorage.set, (key, value))); vm.expectEmit(address(drippie)); emit DripExecuted(dripName, dripName, address(this), block.timestamp); @@ -383,7 +379,7 @@ contract Drippie_Test is Test { bytes32 valueOne = bytes32(uint256(3)); actions[0] = Drippie.DripAction({ target: payable(address(simpleStorage)), - data: abi.encodeWithSelector(simpleStorage.set.selector, keyOne, valueOne), + data: abi.encodeCall(SimpleStorage.set, (keyOne, valueOne)), value: 0 }); @@ -391,7 +387,7 @@ contract Drippie_Test is Test { bytes32 valueTwo = bytes32(uint256(5)); actions[1] = Drippie.DripAction({ target: payable(address(simpleStorage)), - data: abi.encodeWithSelector(simpleStorage.set.selector, keyTwo, valueTwo), + data: abi.encodeCall(SimpleStorage.set, (keyTwo, valueTwo)), value: 0 }); @@ -407,9 +403,9 @@ contract Drippie_Test is Test { vm.expectCall(drippie.dripConfigCheckAddress(dripName), drippie.dripConfigCheckParams(dripName)); - vm.expectCall(address(simpleStorage), 0, abi.encodeWithSelector(SimpleStorage.set.selector, keyOne, valueOne)); + vm.expectCall(address(simpleStorage), 0, abi.encodeCall(SimpleStorage.set, (keyOne, valueOne))); - vm.expectCall(address(simpleStorage), 0, abi.encodeWithSelector(SimpleStorage.set.selector, keyTwo, valueTwo)); + vm.expectCall(address(simpleStorage), 0, abi.encodeCall(SimpleStorage.set, (keyTwo, valueTwo))); vm.expectEmit(address(drippie)); emit DripExecuted(dripName, dripName, address(this), block.timestamp); @@ -463,7 +459,7 @@ contract Drippie_Test is Test { function test_not_active_reverts() external { _createDefaultDrip(dripName); - Drippie.DripStatus status = drippie.dripStatus(dripName); + Drippie.DripStatus status = drippie.getDripStatus(dripName); assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED)); vm.prank(drippie.owner()); diff --git a/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol b/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol index a0ef75f28d55f..b5f156cf1a856 100644 --- a/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol +++ b/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol @@ -103,6 +103,24 @@ contract FaucetTest is Faucet_Initializer { assertEq(faucet.ADMIN(), faucetContractAdmin); } + function test_configure_whenAdmin_succeeds() external { + vm.startPrank(faucetContractAdmin); + faucet.configure(optimistNftFam, Faucet.ModuleConfig("OptimistNftModule", true, 1 days, 1 ether)); + + (string memory name, bool enabled, uint256 ttl, uint256 amount) = faucet.modules(optimistNftFam); + assertEq(name, "OptimistNftModule"); + assertEq(enabled, true); + assertEq(ttl, 1 days); + assertEq(amount, 1 ether); + + assertTrue(faucet.isModuleEnabled(optimistNftFam)); + } + + function test_configure_whenNotAdmin_reverts() external { + vm.expectRevert("Faucet: function can only be called by admin"); + faucet.configure(optimistNftFam, Faucet.ModuleConfig("OptimistNftModule", true, 1 days, 1 ether)); + } + function test_authAdmin_drip_succeeds() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); diff --git a/packages/contracts-bedrock/test/periphery/op-nft/AttestationStation.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/AttestationStation.t.sol deleted file mode 100644 index 4c9b72254d347..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/AttestationStation.t.sol +++ /dev/null @@ -1,115 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/* Testing utilities */ -import { Test } from "forge-std/Test.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; - -contract AttestationStation_Initializer is Test { - address alice_attestor = address(128); - address bob = address(256); - address sally = address(512); - - function setUp() public { - // Give alice and bob some ETH - vm.deal(alice_attestor, 1 ether); - - vm.label(alice_attestor, "alice_attestor"); - vm.label(bob, "bob"); - vm.label(sally, "sally"); - } -} - -contract AttestationStationTest is AttestationStation_Initializer { - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - function test_attest_individual_succeeds() external { - AttestationStation attestationStation = new AttestationStation(); - - vm.expectEmit(true, true, true, true); - emit AttestationCreated(alice_attestor, bob, bytes32("foo"), bytes("bar")); - - vm.prank(alice_attestor); - attestationStation.attest({ _about: bob, _key: bytes32("foo"), _val: bytes("bar") }); - } - - function test_attest_single_succeeds() external { - AttestationStation attestationStation = new AttestationStation(); - - AttestationStation.AttestationData[] memory attestationDataArr = new AttestationStation.AttestationData[](1); - - // alice is going to attest about bob - AttestationStation.AttestationData memory attestationData = AttestationStation.AttestationData({ - about: bob, - key: bytes32("test-key:string"), - val: bytes("test-value") - }); - - // assert the attestation starts empty - assertEq(attestationStation.attestations(alice_attestor, attestationData.about, attestationData.key), ""); - - // make attestation - vm.prank(alice_attestor); - attestationDataArr[0] = attestationData; - attestationStation.attest(attestationDataArr); - - // assert the attestation is there - assertEq( - attestationStation.attestations(alice_attestor, attestationData.about, attestationData.key), - attestationData.val - ); - - bytes memory new_val = bytes("new updated value"); - // make a new attestations to same about and key - attestationData = - AttestationStation.AttestationData({ about: attestationData.about, key: attestationData.key, val: new_val }); - - vm.prank(alice_attestor); - attestationDataArr[0] = attestationData; - attestationStation.attest(attestationDataArr); - - // assert the attestation is updated - assertEq( - attestationStation.attestations(alice_attestor, attestationData.about, attestationData.key), - attestationData.val - ); - } - - function test_attest_bulk_succeeds() external { - AttestationStation attestationStation = new AttestationStation(); - - vm.prank(alice_attestor); - - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](3); - attestationData[0] = AttestationStation.AttestationData({ - about: bob, - key: bytes32("test-key:string"), - val: bytes("test-value") - }); - - attestationData[1] = - AttestationStation.AttestationData({ about: bob, key: bytes32("test-key2"), val: bytes("test-value2") }); - - attestationData[2] = AttestationStation.AttestationData({ - about: sally, - key: bytes32("test-key:string"), - val: bytes("test-value3") - }); - - attestationStation.attest(attestationData); - - // assert the attestations are there - assertEq( - attestationStation.attestations(alice_attestor, attestationData[0].about, attestationData[0].key), - attestationData[0].val - ); - assertEq( - attestationStation.attestations(alice_attestor, attestationData[1].about, attestationData[1].key), - attestationData[1].val - ); - assertEq( - attestationStation.attestations(alice_attestor, attestationData[2].about, attestationData[2].key), - attestationData[2].val - ); - } -} diff --git a/packages/contracts-bedrock/test/periphery/op-nft/Optimist.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/Optimist.t.sol deleted file mode 100644 index 912b04ab33f6d..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/Optimist.t.sol +++ /dev/null @@ -1,547 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; - -// Testing utilities -import { Test } from "forge-std/Test.sol"; -import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { Optimist } from "src/periphery/op-nft/Optimist.sol"; -import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -library Multicall { - bytes internal constant code = - hex"6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033"; - address internal constant addr = 0xcA11bde05977b3631167028862bE2a173976CA11; -} - -contract Optimist_Initializer is Test { - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - event Initialized(uint8); - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - string constant name = "Optimist name"; - string constant symbol = "OPTIMISTSYMBOL"; - string constant base_uri = "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes"; - AttestationStation attestationStation; - Optimist optimist; - OptimistAllowlist optimistAllowlist; - OptimistInviter optimistInviter; - - // Helps with EIP-712 signature generation - OptimistInviterHelper optimistInviterHelper; - - // To test multicall for claiming and minting in one call - IMulticall3 multicall3; - - address internal carol_baseURIAttestor; - address internal alice_allowlistAttestor; - address internal eve_inviteGranter; - address internal ted_coinbaseAttestor; - address internal bob; - address internal sally; - - /// @notice BaseURI attestor sets the baseURI of the Optimist NFT. - function _attestBaseURI(string memory _baseUri) internal { - bytes32 baseURIAttestationKey = optimist.BASE_URI_ATTESTATION_KEY(); - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - attestationData[0] = - AttestationStation.AttestationData(address(optimist), baseURIAttestationKey, bytes(_baseUri)); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated(carol_baseURIAttestor, address(optimist), baseURIAttestationKey, bytes(_baseUri)); - vm.prank(carol_baseURIAttestor); - attestationStation.attest(attestationData); - } - - /// @notice Allowlist attestor creates an attestation for an address. - function _attestAllowlist(address _about) internal { - bytes32 attestationKey = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = - AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") }); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated(alice_allowlistAttestor, _about, attestationKey, bytes("true")); - - vm.prank(alice_allowlistAttestor); - attestationStation.attest(attestationData); - - assertTrue(optimist.isOnAllowList(_about)); - } - - /// @notice Coinbase Quest attestor creates an attestation for an address. - function _attestCoinbaseQuest(address _about) internal { - bytes32 attestationKey = optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(); - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = - AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") }); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated(ted_coinbaseAttestor, _about, attestationKey, bytes("true")); - - vm.prank(ted_coinbaseAttestor); - attestationStation.attest(attestationData); - - assertTrue(optimist.isOnAllowList(_about)); - } - - /// @notice Issues invite, then claims it using the claimer's address. - function _inviteAndClaim(address _about) internal { - uint256 inviterPrivateKey = 0xbeefbeef; - address inviter = vm.addr(inviterPrivateKey); - - address[] memory addresses = new address[](1); - addresses[0] = inviter; - - vm.prank(eve_inviteGranter); - - // grant invites to Inviter; - optimistInviter.setInviteCounts(addresses, 3); - - // issue a new invite - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(inviter); - - // EIP-712 sign with Inviter's private key - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes32 hashedCommit = keccak256(abi.encode(_about, signature)); - - // commit the invite - vm.prank(_about); - optimistInviter.commitInvite(hashedCommit); - - // wait minimum commitment period - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - - // reveal and claim the invite - optimistInviter.claimInvite(_about, claimableInvite, signature); - - assertTrue(optimist.isOnAllowList(_about)); - } - - /// @notice Mocks the allowlistAttestor to always return true for a given address. - function _mockAllowlistTrueFor(address _claimer) internal { - vm.mockCall( - address(optimistAllowlist), - abi.encodeWithSelector(OptimistAllowlist.isAllowedToMint.selector, _claimer), - abi.encode(true) - ); - - assertTrue(optimist.isOnAllowList(_claimer)); - } - - /// @notice Returns address as uint256. - function _getTokenId(address _owner) internal pure returns (uint256) { - return uint256(uint160(address(_owner))); - } - - function setUp() public { - carol_baseURIAttestor = makeAddr("carol_baseURIAttestor"); - alice_allowlistAttestor = makeAddr("alice_allowlistAttestor"); - eve_inviteGranter = makeAddr("eve_inviteGranter"); - ted_coinbaseAttestor = makeAddr("ted_coinbaseAttestor"); - bob = makeAddr("bob"); - sally = makeAddr("sally"); - _initializeContracts(); - } - - function _initializeContracts() internal { - attestationStation = new AttestationStation(); - vm.expectEmit(true, true, false, false); - emit Initialized(1); - - optimistInviter = - new OptimistInviter({ _inviteGranter: eve_inviteGranter, _attestationStation: attestationStation }); - - optimistInviter.initialize("OptimistInviter"); - - // Initialize the helper which helps sign EIP-712 signatures - optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); - - optimistAllowlist = new OptimistAllowlist({ - _attestationStation: attestationStation, - _allowlistAttestor: alice_allowlistAttestor, - _coinbaseQuestAttestor: ted_coinbaseAttestor, - _optimistInviter: address(optimistInviter) - }); - - optimist = new Optimist({ - _name: name, - _symbol: symbol, - _baseURIAttestor: carol_baseURIAttestor, - _attestationStation: attestationStation, - _optimistAllowlist: optimistAllowlist - }); - - multicall3 = IMulticall3(Multicall.addr); - vm.etch(Multicall.addr, Multicall.code); - } -} - -contract OptimistTest is Optimist_Initializer { - /// @notice Check that constructor and initializer parameters are correctly set. - function test_initialize_succeeds() external view { - // expect name to be set - assertEq(optimist.name(), name); - // expect symbol to be set - assertEq(optimist.symbol(), symbol); - // expect attestationStation to be set - assertEq(address(optimist.ATTESTATION_STATION()), address(attestationStation)); - assertEq(optimist.BASE_URI_ATTESTOR(), carol_baseURIAttestor); - } - - /// @notice Bob should be able to mint an NFT if he is allowlisted - /// by the allowlistAttestor and has a balance of 0. - function test_mint_afterAllowlistAttestation_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // allowlist bob - _attestAllowlist(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Bob should be able to mint an NFT if he claimed an invite through OptimistInviter - /// and has a balance of 0. - function test_mint_afterInviteClaimed_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // bob claims an invite - _inviteAndClaim(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Bob should be able to mint an NFT if he has an attestation from Coinbase Quest - /// attestor and has a balance of 0. - function test_mint_afterCoinbaseQuestAttestation_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // bob receives attestation from Coinbase Quest attestor - _attestCoinbaseQuest(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Multiple valid attestations should allow Bob to mint. - function test_mint_afterMultipleAttestations_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // bob receives attestation from Coinbase Quest attestor - _attestCoinbaseQuest(bob); - - // allowlist bob - _attestAllowlist(bob); - - // bob claims an invite - _inviteAndClaim(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Sally should be able to mint a token on behalf of bob. - function test_mint_secondaryMinter_succeeds() external { - _mockAllowlistTrueFor(bob); - - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - - // mint as sally instead of bob - vm.prank(sally); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Bob should not be able to mint an NFT if he is not allowlisted. - function test_mint_forNonAllowlistedClaimer_reverts() external { - vm.prank(bob); - vm.expectRevert("Optimist: address is not on allowList"); - optimist.mint(bob); - } - - /// @notice Bob's tx should revert if he already minted. - function test_mint_forAlreadyMintedClaimer_reverts() external { - _attestAllowlist(bob); - - // mint initial nft with bob - vm.prank(bob); - optimist.mint(bob); - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - - // attempt to mint again - vm.expectRevert("ERC721: token already minted"); - optimist.mint(bob); - } - - /// @notice The baseURI should be set by attestation station by the baseURIAttestor. - function test_baseURI_returnsCorrectBaseURI_succeeds() external { - _attestBaseURI(base_uri); - - bytes memory data = abi.encodeWithSelector( - attestationStation.attestations.selector, - carol_baseURIAttestor, - address(optimist), - optimist.BASE_URI_ATTESTATION_KEY() - ); - vm.expectCall(address(attestationStation), data); - vm.prank(carol_baseURIAttestor); - - // assert baseURI is set - assertEq(optimist.baseURI(), base_uri); - } - - /// @notice tokenURI should return the token uri for a minted token. - function test_tokenURI_returnsCorrectTokenURI_succeeds() external { - // we are using true but it can be any non empty value - _attestBaseURI(base_uri); - - // mint an NFT - _mockAllowlistTrueFor(bob); - vm.prank(bob); - optimist.mint(bob); - - // assert tokenURI is set - assertEq(optimist.baseURI(), base_uri); - assertEq( - optimist.tokenURI(_getTokenId(bob)), - "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes/0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e.json" - ); - } - - /// @notice Should return the token id of the owner. - function test_tokenIdOfAddress_returnsOwnerID_succeeds() external { - uint256 willTokenId = 1024; - address will = address(1024); - - _mockAllowlistTrueFor(will); - - optimist.mint(will); - - assertEq(optimist.tokenIdOfAddress(will), willTokenId); - } - - /// @notice transferFrom should revert since Optimist is a SBT. - function test_transferFrom_soulbound_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - // attempt to transfer to sally - vm.expectRevert(bytes("Optimist: soul bound token")); - vm.prank(bob); - optimist.transferFrom(bob, sally, _getTokenId(bob)); - - // attempt to transfer to sally - vm.expectRevert(bytes("Optimist: soul bound token")); - vm.prank(bob); - optimist.safeTransferFrom(bob, sally, _getTokenId(bob)); - // attempt to transfer to sally - vm.expectRevert(bytes("Optimist: soul bound token")); - vm.prank(bob); - optimist.safeTransferFrom(bob, sally, _getTokenId(bob), bytes("0x")); - } - - /// @notice approve should revert since Optimist is a SBT. - function test_approve_soulbound_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - // attempt to approve sally - vm.prank(bob); - vm.expectRevert("Optimist: soul bound token"); - optimist.approve(address(attestationStation), _getTokenId(bob)); - - assertEq(optimist.getApproved(_getTokenId(bob)), address(0)); - } - - /// @notice setApprovalForAll should revert since Optimist is a SBT. - function test_setApprovalForAll_soulbound_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - vm.prank(alice_allowlistAttestor); - vm.expectRevert(bytes("Optimist: soul bound token")); - optimist.setApprovalForAll(alice_allowlistAttestor, true); - - // expect approval amount to stil be 0 - assertEq(optimist.getApproved(_getTokenId(bob)), address(0)); - // isApprovedForAll should return false - assertEq(optimist.isApprovedForAll(alice_allowlistAttestor, alice_allowlistAttestor), false); - } - - /// @notice Only owner should be able to burn token. - function test_burn_byOwner_succeeds() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - // burn as bob - vm.prank(bob); - optimist.burn(_getTokenId(bob)); - - // expect bob to have no balance now - assertEq(optimist.balanceOf(bob), 0); - } - - /// @notice Non-owner attempting to burn token should revert. - function test_burn_byNonOwner_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - vm.expectRevert("ERC721: caller is not token owner nor approved"); - // burn as Sally - vm.prank(sally); - optimist.burn(_getTokenId(bob)); - - // expect bob to have still have the token - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Should support ERC-721 interface. - function test_supportsInterface_returnsCorrectInterfaceForERC721_succeeds() external view { - bytes4 iface721 = type(IERC721).interfaceId; - // check that it supports ERC-721 interface - assertEq(optimist.supportsInterface(iface721), true); - } - - /// @notice Checking that multi-call using the invite & claim flow works correctly, since the - /// frontend will be making multicalls to improve UX. The OptimistInviter.claimInvite - /// and Optimist.mint will be batched - function test_multicall_batchingClaimAndMint_succeeds() external { - uint256 inviterPrivateKey = 0xbeefbeef; - address inviter = vm.addr(inviterPrivateKey); - - address[] memory addresses = new address[](1); - addresses[0] = inviter; - - vm.prank(eve_inviteGranter); - - // grant invites to Inviter; - optimistInviter.setInviteCounts(addresses, 3); - - // issue a new invite - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(inviter); - - // EIP-712 sign with Inviter's private key - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes32 hashedCommit = keccak256(abi.encode(bob, signature)); - - // commit the invite - vm.prank(bob); - optimistInviter.commitInvite(hashedCommit); - - // wait minimum commitment period - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - - IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2); - - // First call is to claim the invite, receiving the attestation - calls[0] = IMulticall3.Call3({ - target: address(optimistInviter), - callData: abi.encodeWithSelector(optimistInviter.claimInvite.selector, bob, claimableInvite, signature), - allowFailure: false - }); - - // Second call is to mint the Optimist NFT - calls[1] = IMulticall3.Call3({ - target: address(optimist), - callData: abi.encodeWithSelector(optimist.mint.selector, bob), - allowFailure: false - }); - - multicall3.aggregate3(calls); - - assertTrue(optimist.isOnAllowList(bob)); - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } -} diff --git a/packages/contracts-bedrock/test/periphery/op-nft/OptimistAllowlist.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/OptimistAllowlist.t.sol deleted file mode 100644 index c0c2aef2bce0c..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/OptimistAllowlist.t.sol +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Testing utilities -import { Test } from "forge-std/Test.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; - -contract OptimistAllowlist_Initializer is Test { - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - address internal alice_allowlistAttestor; - address internal sally_coinbaseQuestAttestor; - address internal ted; - - uint256 internal bobPrivateKey; - address internal bob; - - AttestationStation attestationStation; - OptimistAllowlist optimistAllowlist; - OptimistInviter optimistInviter; - - // Helps with EIP-712 signature generation - OptimistInviterHelper optimistInviterHelper; - - function setUp() public { - alice_allowlistAttestor = makeAddr("alice_allowlistAttestor"); - sally_coinbaseQuestAttestor = makeAddr("sally_coinbaseQuestAttestor"); - ted = makeAddr("ted"); - - bobPrivateKey = 0xB0B0B0B0; - bob = vm.addr(bobPrivateKey); - vm.label(bob, "bob"); - - // Give alice and bob and sally some ETH - vm.deal(alice_allowlistAttestor, 1 ether); - vm.deal(sally_coinbaseQuestAttestor, 1 ether); - vm.deal(bob, 1 ether); - vm.deal(ted, 1 ether); - - _initializeContracts(); - } - - function attestAllowlist(address _about) internal { - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = AttestationStation.AttestationData({ - about: _about, - key: optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), - val: bytes("true") - }); - vm.prank(alice_allowlistAttestor); - attestationStation.attest(attestationData); - } - - function attestCoinbaseQuest(address _about) internal { - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = AttestationStation.AttestationData({ - about: _about, - key: optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(), - val: bytes("true") - }); - vm.prank(sally_coinbaseQuestAttestor); - attestationStation.attest(attestationData); - } - - function inviteAndClaim(address claimer) internal { - address[] memory addresses = new address[](1); - addresses[0] = bob; - - vm.prank(alice_allowlistAttestor); - - // grant invites to Bob; - optimistInviter.setInviteCounts(addresses, 3); - - // issue a new invite - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(bob); - - // EIP-712 sign with Bob's private key - bytes memory signature = _getSignature(bobPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - - bytes32 hashedCommit = keccak256(abi.encode(claimer, signature)); - - // commit the invite - vm.prank(claimer); - optimistInviter.commitInvite(hashedCommit); - - // wait minimum commitment period - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - - // reveal and claim the invite - optimistInviter.claimInvite(claimer, claimableInvite, signature); - } - - /// @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs. - function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) internal pure returns (bytes memory) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest); - - bytes memory signature = abi.encodePacked(r, s, v); - return signature; - } - - function _initializeContracts() internal { - attestationStation = new AttestationStation(); - - optimistInviter = new OptimistInviter(alice_allowlistAttestor, attestationStation); - optimistInviter.initialize("OptimistInviter"); - - optimistAllowlist = new OptimistAllowlist( - attestationStation, alice_allowlistAttestor, sally_coinbaseQuestAttestor, address(optimistInviter) - ); - - optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); - } -} - -contract OptimistAllowlistTest is OptimistAllowlist_Initializer { - function test_constructor_succeeds() external view { - // expect attestationStation to be set - assertEq(address(optimistAllowlist.ATTESTATION_STATION()), address(attestationStation)); - assertEq(optimistAllowlist.ALLOWLIST_ATTESTOR(), alice_allowlistAttestor); - assertEq(optimistAllowlist.COINBASE_QUEST_ATTESTOR(), sally_coinbaseQuestAttestor); - assertEq(address(optimistAllowlist.OPTIMIST_INVITER()), address(optimistInviter)); - } - - /// @notice Base case, a account without any relevant attestations should not be able to mint. - function test_isAllowedToMint_withoutAnyAttestations_fails() external view { - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice After receiving a valid allowlist attestation, the account should be able to mint. - function test_isAllowedToMint_fromAllowlistAttestor_succeeds() external { - attestAllowlist(bob); - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice After receiving a valid attestation from the Coinbase Quest attestor, - /// the account should be able to mint. - function test_isAllowedToMint_fromCoinbaseQuestAttestor_succeeds() external { - attestCoinbaseQuest(bob); - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Account that received an attestation from the OptimistInviter contract by going - /// through the claim invite flow should be able to mint. - function test_isAllowedToMint_fromInvite_succeeds() external { - inviteAndClaim(bob); - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Attestation from the wrong allowlist attestor should not allow minting. - function test_isAllowedToMint_fromWrongAllowlistAttestor_fails() external { - // Ted is not the allowlist attestor - vm.prank(ted); - attestationStation.attest(bob, optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), bytes("true")); - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Coinbase quest attestation from wrong attestor should not allow minting. - function test_isAllowedToMint_fromWrongCoinbaseQuestAttestor_fails() external { - // Ted is not the coinbase quest attestor - vm.prank(ted); - attestationStation.attest(bob, optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(), bytes("true")); - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Claiming an invite on the non-official OptimistInviter contract should not allow - /// minting. - function test_isAllowedToMint_fromWrongOptimistInviter_fails() external { - vm.prank(ted); - attestationStation.attest(bob, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, bytes("true")); - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Having multiple signals, even if one is invalid, should still allow minting. - function test_isAllowedToMint_withMultipleAttestations_succeeds() external { - attestAllowlist(bob); - attestCoinbaseQuest(bob); - inviteAndClaim(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // A invalid attestation, as Ted is not allowlist attestor - vm.prank(ted); - attestationStation.attest(bob, optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), bytes("true")); - - // Since Bob has at least one valid attestation, he should be allowed to mint - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Having falsy attestation value should not allow minting. - function test_isAllowedToMint_fromAllowlistAttestorWithFalsyValue_fails() external { - // First sends correct attestation - attestAllowlist(bob); - - bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); - vm.expectEmit(true, true, true, false); - emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes("dsafsds")); - - // Invalidates existing attestation - vm.prank(alice_allowlistAttestor); - attestationStation.attest(bob, key, bytes("")); - - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Having falsy attestation value from Coinbase attestor should not allow minting. - function test_isAllowedToMint_fromCoinbaseQuestAttestorWithFalsyValue_fails() external { - // First sends correct attestation - attestAllowlist(bob); - - bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); - vm.expectEmit(true, true, true, true); - emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes("")); - - // Invalidates existing attestation - vm.prank(alice_allowlistAttestor); - attestationStation.attest(bob, key, bytes("")); - - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } -} diff --git a/packages/contracts-bedrock/test/periphery/op-nft/OptimistInviter.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/OptimistInviter.t.sol deleted file mode 100644 index 58e71a13a8d77..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/OptimistInviter.t.sol +++ /dev/null @@ -1,529 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Testing utilities -import { Test } from "forge-std/Test.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { Optimist } from "src/periphery/op-nft/Optimist.sol"; -import { TestERC1271Wallet } from "test/mocks/TestERC1271Wallet.sol"; -import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; - -contract OptimistInviter_Initializer is Test { - event InviteClaimed(address indexed issuer, address indexed claimer); - event Initialized(uint8 version); - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - bytes32 EIP712_DOMAIN_TYPEHASH; - - address internal alice_inviteGranter; - address internal sally; - address internal ted; - address internal eve; - - address internal bob; - uint256 internal bobPrivateKey; - address internal carol; - uint256 internal carolPrivateKey; - - TestERC1271Wallet carolERC1271Wallet; - - AttestationStation attestationStation; - OptimistInviter optimistInviter; - - OptimistInviterHelper optimistInviterHelper; - - function setUp() public { - alice_inviteGranter = makeAddr("alice_inviteGranter"); - sally = makeAddr("sally"); - ted = makeAddr("ted"); - eve = makeAddr("eve"); - - bobPrivateKey = 0xB0B0B0B0; - bob = vm.addr(bobPrivateKey); - - carolPrivateKey = 0xC0C0C0C0; - carol = vm.addr(carolPrivateKey); - - carolERC1271Wallet = new TestERC1271Wallet(carol); - - // Give alice and bob and sally some ETH - vm.deal(alice_inviteGranter, 1 ether); - vm.deal(bob, 1 ether); - vm.deal(sally, 1 ether); - vm.deal(ted, 1 ether); - vm.deal(eve, 1 ether); - - EIP712_DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - _initializeContracts(); - } - - /// @notice Instantiates an AttestationStation, and an OptimistInviter. - function _initializeContracts() internal { - attestationStation = new AttestationStation(); - - optimistInviter = new OptimistInviter(alice_inviteGranter, attestationStation); - - vm.expectEmit(true, true, true, true, address(optimistInviter)); - emit Initialized(1); - optimistInviter.initialize("OptimistInviter"); - - optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); - } - - function _passMinCommitmentPeriod() internal { - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - } - - /// @notice Returns a user's current invite count, as stored in the AttestationStation. - function _getInviteCount(address _issuer) internal view returns (uint256) { - return optimistInviter.inviteCounts(_issuer); - } - - /// @notice Returns true if claimer has the proper attestation from OptimistInviter to mint. - function _hasMintAttestation(address _claimer) internal view returns (bool) { - bytes memory attestation = attestationStation.attestations( - address(optimistInviter), _claimer, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY - ); - return attestation.length > 0; - } - - /// @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs. - function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) internal pure returns (bytes memory) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest); - - bytes memory signature = abi.encodePacked(r, s, v); - return signature; - } - - /// @notice Signs a claimable invite with the given private key and returns the signature using - /// correct EIP712 domain separator. - function _issueInviteAs(uint256 _privateKey) - internal - returns (OptimistInviter.ClaimableInvite memory, bytes memory) - { - return _issueInviteWithEIP712Domain( - _privateKey, - bytes("OptimistInviter"), - bytes(optimistInviter.EIP712_VERSION()), - block.chainid, - address(optimistInviter) - ); - } - - /// @notice Signs a claimable invite with the given private key and returns the signature using - /// the given EIP712 domain separator. This assumes that the issuer's address is the - /// corresponding public key to _issuerPrivateKey. - function _issueInviteWithEIP712Domain( - uint256 _issuerPrivateKey, - bytes memory _eip712Name, - bytes memory _eip712Version, - uint256 _eip712Chainid, - address _eip712VerifyingContract - ) - internal - returns (OptimistInviter.ClaimableInvite memory, bytes memory) - { - address issuer = vm.addr(_issuerPrivateKey); - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(issuer); - return ( - claimableInvite, - _getSignature( - _issuerPrivateKey, - optimistInviterHelper.getDigestWithEIP712Domain( - claimableInvite, _eip712Name, _eip712Version, _eip712Chainid, _eip712VerifyingContract - ) - ) - ); - } - - /// @notice Commits a signature and claimer address to the OptimistInviter contract. - function _commitInviteAs(address _as, bytes memory _signature) internal { - vm.prank(_as); - bytes32 hashedSignature = keccak256(abi.encode(_as, _signature)); - optimistInviter.commitInvite(hashedSignature); - - // Check that the commitment was stored correctly - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - } - - /// @notice Signs a claimable invite with the given private key. The claimer commits then claims - /// the invite. Checks that all expected events are emitted and that state is updated - /// correctly. Returns the signature and invite for use in tests. - function _issueThenClaimShouldSucceed( - uint256 _issuerPrivateKey, - address _claimer - ) - internal - returns (OptimistInviter.ClaimableInvite memory, bytes memory) - { - address issuer = vm.addr(_issuerPrivateKey); - uint256 prevInviteCount = _getInviteCount(issuer); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = - _issueInviteAs(_issuerPrivateKey); - - _commitInviteAs(_claimer, signature); - - // The hash(claimer ++ signature) should be committed - assertEq(optimistInviter.commitmentTimestamps(keccak256(abi.encode(_claimer, signature))), block.timestamp); - - _passMinCommitmentPeriod(); - - // OptimistInviter should issue a new attestation allowing claimer to mint - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - _claimer, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(issuer) - ); - - // Should emit an event indicating that the invite was claimed - vm.expectEmit(true, false, false, false, address(optimistInviter)); - emit InviteClaimed(issuer, _claimer); - - vm.prank(_claimer); - optimistInviter.claimInvite(_claimer, claimableInvite, signature); - - // The nonce that issuer used should be marked as used - assertTrue(optimistInviter.usedNonces(issuer, claimableInvite.nonce)); - - // Issuer should have one less invite - assertEq(prevInviteCount - 1, _getInviteCount(issuer)); - - // Claimer should have the mint attestation from the OptimistInviter contract - assertTrue(_hasMintAttestation(_claimer)); - - return (claimableInvite, signature); - } - - /// @notice Issues 3 invites to the given address. Checks that all expected events are emitted - /// and that state is updated correctly. - function _grantInvitesTo(address _to) internal { - address[] memory addresses = new address[](1); - addresses[0] = _to; - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), _to, optimistInviter.CAN_INVITE_ATTESTATION_KEY(), bytes("true") - ); - - vm.prank(alice_inviteGranter); - optimistInviter.setInviteCounts(addresses, 3); - - assertEq(_getInviteCount(_to), 3); - } -} - -contract OptimistInviterTest is OptimistInviter_Initializer { - function test_initialize_succeeds() external view { - // expect attestationStation to be set - assertEq(address(optimistInviter.ATTESTATION_STATION()), address(attestationStation)); - assertEq(optimistInviter.INVITE_GRANTER(), alice_inviteGranter); - } - - /// @notice Alice the admin should be able to give Bob, Sally, and Carol 3 invites, and the - /// OptimistInviter contract should increment invite counts on inviteCounts and issue - /// 'optimist.can-invite' attestations. - function test_grantInvites_adminAddingInvites_succeeds() external { - address[] memory addresses = new address[](3); - addresses[0] = bob; - addresses[1] = sally; - addresses[2] = address(carolERC1271Wallet); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), bob, optimistInviter.CAN_INVITE_ATTESTATION_KEY(), bytes("true") - ); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), sally, optimistInviter.CAN_INVITE_ATTESTATION_KEY(), bytes("true") - ); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - address(carolERC1271Wallet), - optimistInviter.CAN_INVITE_ATTESTATION_KEY(), - bytes("true") - ); - - vm.prank(alice_inviteGranter); - optimistInviter.setInviteCounts(addresses, 3); - - assertEq(_getInviteCount(bob), 3); - assertEq(_getInviteCount(sally), 3); - assertEq(_getInviteCount(address(carolERC1271Wallet)), 3); - } - - /// @notice Bob, who is not the invite granter, should not be able to issue invites. - function test_grantInvites_nonAdminAddingInvites_reverts() external { - address[] memory addresses = new address[](2); - addresses[0] = bob; - addresses[1] = sally; - - vm.expectRevert("OptimistInviter: only invite granter can grant invites"); - vm.prank(bob); - optimistInviter.setInviteCounts(addresses, 3); - } - - /// @notice Sally should be able to commit an invite given by by Bob. - function test_commitInvite_committingForYourself_succeeds() external { - _grantInvitesTo(bob); - (, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(sally); - bytes32 hashedSignature = keccak256(abi.encode(sally, signature)); - optimistInviter.commitInvite(hashedSignature); - - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - } - - /// @notice Sally should be able to Bob's for a different claimer, Eve. - function test_commitInvite_committingForSomeoneElse_succeeds() external { - _grantInvitesTo(bob); - (, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(sally); - bytes32 hashedSignature = keccak256(abi.encode(eve, signature)); - optimistInviter.commitInvite(hashedSignature); - - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - } - - /// @notice Attempting to commit the same hash twice should revert. This prevents griefing. - function test_commitInvite_committingSameHashTwice_reverts() external { - _grantInvitesTo(bob); - (, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(sally); - bytes32 hashedSignature = keccak256(abi.encode(eve, signature)); - optimistInviter.commitInvite(hashedSignature); - - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - - vm.expectRevert("OptimistInviter: commitment already made"); - optimistInviter.commitInvite(hashedSignature); - } - - /// @notice Bob issues signature, and Sally claims the invite. Bob's invite count should be - /// decremented, and Sally should be able to mint. - function test_claimInvite_succeeds() external { - _grantInvitesTo(bob); - _issueThenClaimShouldSucceed(bobPrivateKey, sally); - } - - /// @notice Bob issues signature, and Ted commits the invite for Sally. Eve claims for Sally. - function test_claimInvite_claimForSomeoneElse_succeeds() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(ted); - optimistInviter.commitInvite(keccak256(abi.encode(sally, signature))); - _passMinCommitmentPeriod(); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - sally, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(bob) - ); - - // Should emit an event indicating that the invite was claimed - vm.expectEmit(true, true, true, true, address(optimistInviter)); - emit InviteClaimed(bob, sally); - - vm.prank(eve); - optimistInviter.claimInvite(sally, claimableInvite, signature); - - assertEq(_getInviteCount(bob), 2); - assertTrue(_hasMintAttestation(sally)); - assertFalse(_hasMintAttestation(eve)); - } - - function test_claimInvite_claimBeforeMinCommitmentPeriod_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - _commitInviteAs(sally, signature); - - // Some time passes, but not enough to meet the minimum commitment period - vm.warp(block.timestamp + 10); - - vm.expectRevert("OptimistInviter: minimum commitment period has not elapsed yet"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Signature issued for previous versions of the contract should fail. - function test_claimInvite_usingSignatureIssuedForDifferentVersion_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteWithEIP712Domain( - bobPrivateKey, "OptimismInviter", "0.9.1", block.chainid, address(optimistInviter) - ); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Replay attack for signature issued for contract on different chain (ie. mainnet) - /// should fail. - function test_claimInvite_usingSignatureIssuedForDifferentChain_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteWithEIP712Domain( - bobPrivateKey, "OptimismInviter", bytes(optimistInviter.EIP712_VERSION()), 1, address(optimistInviter) - ); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Replay attack for signature issued for instantiation of the OptimistInviter contract - /// on a different address should fail. - function test_claimInvite_usingSignatureIssuedForDifferentContract_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteWithEIP712Domain( - bobPrivateKey, "OptimismInviter", bytes(optimistInviter.EIP712_VERSION()), block.chainid, address(0xBEEF) - ); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Attempting to claim again using the same signature again should fail. - function test_claimInvite_replayingUsedNonce_reverts() external { - _grantInvitesTo(bob); - - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = - _issueThenClaimShouldSucceed(bobPrivateKey, sally); - - // Sally tries to claim the invite using the same signature - vm.expectRevert("OptimistInviter: nonce has already been used"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - - // Carol tries to claim the invite using the same signature - _commitInviteAs(carol, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: nonce has already been used"); - vm.prank(carol); - optimistInviter.claimInvite(carol, claimableInvite, signature); - } - - /// @notice Issuing signatures through a contract that implements ERC1271 should succeed (ie. - /// Gnosis Safe or other smart contract wallets). Carol is using a ERC1271 contract - /// wallet that is simply backed by her private key. - function test_claimInvite_usingERC1271Wallet_succeeds() external { - _grantInvitesTo(address(carolERC1271Wallet)); - - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(address(carolERC1271Wallet)); - - bytes memory signature = _getSignature(carolPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - - // Sally tries to claim the invite - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - sally, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(address(carolERC1271Wallet)) - ); - - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - assertEq(_getInviteCount(address(carolERC1271Wallet)), 2); - } - - /// @notice Claimer must commit the signature before claiming the invite. Sally attempts to - /// claim the Bob's invite without committing the signature first. - function test_claimInvite_withoutCommittingHash_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.expectRevert("OptimistInviter: claimer and signature have not been committed yet"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Using a signature that doesn't correspond to the claimable invite should fail. - function test_claimInvite_withIncorrectSignature_reverts() external { - _grantInvitesTo(carol); - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory bobClaimableInvite, bytes memory bobSignature) = - _issueInviteAs(bobPrivateKey); - (, bytes memory carolSignature) = _issueInviteAs(carolPrivateKey); - - _commitInviteAs(sally, bobSignature); - _commitInviteAs(sally, carolSignature); - - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, bobClaimableInvite, carolSignature); - } - - /// @notice Attempting to use a signature from a issuer who never was granted invites should - /// fail. - function test_claimInvite_whenIssuerNeverReceivedInvites_reverts() external { - // Bob was never granted any invites, but issues an invite for Eve - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: issuer has no invites"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Attempting to use a signature from a issuer who has no more invites should fail. - /// Bob has 3 invites, but issues 4 invites for Sally, Carol, Ted, and Eve. Only the - /// first 3 invites should be claimable. The last claimer, Eve, should not be able to - /// claim the invite. - function test_claimInvite_whenIssuerHasNoInvitesLeft_reverts() external { - _grantInvitesTo(bob); - - _issueThenClaimShouldSucceed(bobPrivateKey, sally); - _issueThenClaimShouldSucceed(bobPrivateKey, carol); - _issueThenClaimShouldSucceed(bobPrivateKey, ted); - - assertEq(_getInviteCount(bob), 0); - - (OptimistInviter.ClaimableInvite memory claimableInvite4, bytes memory signature4) = - _issueInviteAs(bobPrivateKey); - - _commitInviteAs(eve, signature4); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: issuer has no invites"); - vm.prank(eve); - optimistInviter.claimInvite(eve, claimableInvite4, signature4); - - assertEq(_getInviteCount(bob), 0); - } -} diff --git a/packages/contracts-bedrock/test/safe-tools/CompatibilityFallbackHandler_1_3_0.sol b/packages/contracts-bedrock/test/safe-tools/CompatibilityFallbackHandler_1_3_0.sol index ac0c264d30382..89536e2e4493d 100644 --- a/packages/contracts-bedrock/test/safe-tools/CompatibilityFallbackHandler_1_3_0.sol +++ b/packages/contracts-bedrock/test/safe-tools/CompatibilityFallbackHandler_1_3_0.sol @@ -93,7 +93,7 @@ contract CompatibilityFallbackHandler is DefaultCallbackHandler, ISignatureValid GnosisSafe safe = GnosisSafe(payable(msg.sender)); bytes32 messageHash = getMessageHashForSafe(safe, _data); if (_signature.length == 0) { - require(safe.signedMessages(messageHash) != 0, "Hash not approved"); + require(safe.signedMessages(messageHash) != 0, "CompatibilityFallbackHandler: Hash not approved"); } else { safe.checkSignatures(messageHash, _data, _signature); } diff --git a/packages/contracts-bedrock/test/safe-tools/SafeTestTools.sol b/packages/contracts-bedrock/test/safe-tools/SafeTestTools.sol index 34386f5e39235..976cae5536914 100644 --- a/packages/contracts-bedrock/test/safe-tools/SafeTestTools.sol +++ b/packages/contracts-bedrock/test/safe-tools/SafeTestTools.sol @@ -260,7 +260,7 @@ library SafeTestLib { instance, address(instance.safe), 0, - abi.encodeWithSelector(ModuleManager.enableModule.selector, module), + abi.encodeCall(ModuleManager.enableModule, (module)), Enum.Operation.Call, 0, 0, @@ -289,7 +289,7 @@ library SafeTestLib { instance, address(instance.safe), 0, - abi.encodeWithSelector(ModuleManager.disableModule.selector, prevModule, module), + abi.encodeCall(ModuleManager.disableModule, (prevModule, module)), Enum.Operation.Call, 0, 0, @@ -308,7 +308,7 @@ library SafeTestLib { instance, address(instance.safe), 0, - abi.encodeWithSelector(GuardManager.setGuard.selector, guard), + abi.encodeCall(GuardManager.setGuard, (guard)), Enum.Operation.Call, 0, 0, @@ -326,7 +326,7 @@ library SafeTestLib { instance: instance, to: signMessageLib, value: 0, - data: abi.encodeWithSelector(SignMessageLib.signMessage.selector, data), + data: abi.encodeCall(SignMessageLib.signMessage, (data)), operation: Enum.Operation.DelegateCall, safeTxGas: 0, baseGas: 0, @@ -350,21 +350,13 @@ library SafeTestLib { /// @dev Adds a new owner to the safe function changeThreshold(SafeInstance memory instance, uint256 threshold) internal { - execTransaction( - instance, - address(instance.safe), - 0, - abi.encodeWithSelector(OwnerManager.changeThreshold.selector, threshold) - ); + execTransaction(instance, address(instance.safe), 0, abi.encodeCall(OwnerManager.changeThreshold, (threshold))); } /// @dev Adds a new owner to the safe function addOwnerWithThreshold(SafeInstance memory instance, address owner, uint256 threshold) internal { execTransaction( - instance, - address(instance.safe), - 0, - abi.encodeWithSelector(OwnerManager.addOwnerWithThreshold.selector, owner, threshold) + instance, address(instance.safe), 0, abi.encodeCall(OwnerManager.addOwnerWithThreshold, (owner, threshold)) ); } @@ -373,10 +365,7 @@ library SafeTestLib { function removeOwner(SafeInstance memory instance, address prevOwner, address owner, uint256 threshold) internal { prevOwner = prevOwner > address(0) ? prevOwner : SafeTestLib.getPrevOwner(instance, owner); execTransaction( - instance, - address(instance.safe), - 0, - abi.encodeWithSelector(OwnerManager.removeOwner.selector, prevOwner, owner, threshold) + instance, address(instance.safe), 0, abi.encodeCall(OwnerManager.removeOwner, (prevOwner, owner, threshold)) ); } @@ -385,10 +374,7 @@ library SafeTestLib { function swapOwner(SafeInstance memory instance, address prevOwner, address oldOwner, address newOwner) internal { prevOwner = prevOwner > address(0) ? prevOwner : SafeTestLib.getPrevOwner(instance, oldOwner); execTransaction( - instance, - address(instance.safe), - 0, - abi.encodeWithSelector(OwnerManager.swapOwner.selector, prevOwner, oldOwner, newOwner) + instance, address(instance.safe), 0, abi.encodeCall(OwnerManager.swapOwner, (prevOwner, oldOwner, newOwner)) ); } @@ -537,16 +523,18 @@ contract SafeTestTools { bytes memory initData = advancedParams.initData.length > 0 ? advancedParams.initData - : abi.encodeWithSelector( - GnosisSafe.setup.selector, - owners, - threshold, - advancedParams.setupModulesCall_to, - advancedParams.setupModulesCall_data, - advancedParams.includeFallbackHandler ? address(handler) : address(0), - advancedParams.refundToken, - advancedParams.refundAmount, - advancedParams.refundReceiver + : abi.encodeCall( + GnosisSafe.setup, + ( + owners, + threshold, + advancedParams.setupModulesCall_to, + advancedParams.setupModulesCall_data, + advancedParams.includeFallbackHandler ? address(handler) : address(0), + advancedParams.refundToken, + advancedParams.refundAmount, + advancedParams.refundReceiver + ) ); DeployedSafe safe0 = DeployedSafe( diff --git a/packages/contracts-bedrock/test/safe/DeployOwnership.t.sol b/packages/contracts-bedrock/test/safe/DeployOwnership.t.sol index 973b888ddf683..835b05b80f6bf 100644 --- a/packages/contracts-bedrock/test/safe/DeployOwnership.t.sol +++ b/packages/contracts-bedrock/test/safe/DeployOwnership.t.sol @@ -42,7 +42,7 @@ contract DeployOwnershipTest is Test, DeployOwnership { /// @dev Test the example Foundation Safe configurations, against the expected configuration, and /// check that they both have the same configuration. - function test_exampleFoundationSafes() public { + function test_exampleFoundationSafes_configuration_succeeds() public { Safe upgradeSafe = Safe(payable(mustGetAddress("FoundationUpgradeSafe"))); Safe operationsSafe = Safe(payable(mustGetAddress("FoundationOperationsSafe"))); SafeConfig memory exampleFoundationConfig = _getExampleFoundationConfig(); @@ -57,7 +57,7 @@ contract DeployOwnershipTest is Test, DeployOwnership { } /// @dev Test the example Security Council Safe configuration. - function test_exampleSecurityCouncilSafe() public { + function test_exampleSecurityCouncilSafe_configuration_succeeds() public { Safe securityCouncilSafe = Safe(payable(mustGetAddress("SecurityCouncilSafe"))); SecurityCouncilConfig memory exampleSecurityCouncilConfig = _getExampleCouncilConfig(); @@ -96,7 +96,7 @@ contract DeployOwnershipTest is Test, DeployOwnership { } /// @dev Test the example Guardian Safe configuration. - function test_exampleGuardianSafe() public view { + function test_exampleGuardianSafe_configuration_succeeds() public view { Safe guardianSafe = Safe(payable(mustGetAddress("GuardianSafe"))); address[] memory owners = new address[](1); owners[0] = mustGetAddress("SecurityCouncilSafe"); diff --git a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol index 84824a5ff6944..21fad7bb547f4 100644 --- a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol @@ -8,15 +8,16 @@ import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; import "test/safe-tools/SafeTestTools.sol"; // Contracts -import { DeputyGuardianModule } from "src/safe/DeputyGuardianModule.sol"; +import { IDeputyGuardianModule } from "interfaces/safe/IDeputyGuardianModule.sol"; // Libraries import "src/dispute/lib/Types.sol"; // Interfaces -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract DeputyGuardianModule_TestInit is CommonTest, SafeTestTools { using SafeTestLib for SafeInstance; @@ -26,13 +27,12 @@ contract DeputyGuardianModule_TestInit is CommonTest, SafeTestTools { event ExecutionFromModuleSuccess(address indexed); - DeputyGuardianModule deputyGuardianModule; + IDeputyGuardianModule deputyGuardianModule; SafeInstance safeInstance; address deputyGuardian; /// @dev Sets up the test environment function setUp() public virtual override { - super.enableFaultProofs(); super.setUp(); // Create a Safe with 10 owners @@ -48,11 +48,16 @@ contract DeputyGuardianModule_TestInit is CommonTest, SafeTestTools { deputyGuardian = makeAddr("deputyGuardian"); - deputyGuardianModule = new DeputyGuardianModule({ - _safe: safeInstance.safe, - _superchainConfig: superchainConfig, - _deputyGuardian: deputyGuardian - }); + deputyGuardianModule = IDeputyGuardianModule( + DeployUtils.create1({ + _name: "DeputyGuardianModule", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IDeputyGuardianModule.__constructor__, (safeInstance.safe, superchainConfig, deputyGuardian) + ) + ) + }) + ); safeInstance.enableModule(address(deputyGuardianModule)); } } @@ -95,7 +100,7 @@ contract DeputyGuardianModule_Pause_TestFail is DeputyGuardianModule_TestInit { function test_pause_targetReverts_reverts() external { vm.mockCallRevert( address(superchainConfig), - abi.encodeWithSelector(superchainConfig.pause.selector), + abi.encodePacked(superchainConfig.pause.selector), "SuperchainConfig: pause() reverted" ); @@ -145,7 +150,7 @@ contract DeputyGuardianModule_Unpause_TestFail is DeputyGuardianModule_Unpause_T function test_unpause_targetReverts_reverts() external { vm.mockCallRevert( address(superchainConfig), - abi.encodeWithSelector(superchainConfig.unpause.selector), + abi.encodePacked(superchainConfig.unpause.selector), "SuperchainConfig: unpause reverted" ); @@ -165,9 +170,7 @@ contract DeputyGuardianModule_SetAnchorState_TestFail is DeputyGuardianModule_Te function test_setAnchorState_targetReverts_reverts() external { IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); vm.mockCallRevert( - address(asr), - abi.encodeWithSelector(asr.setAnchorState.selector), - "AnchorStateRegistry: setAnchorState reverted" + address(asr), abi.encodePacked(asr.setAnchorState.selector), "AnchorStateRegistry: setAnchorState reverted" ); vm.prank(address(deputyGuardian)); vm.expectRevert( @@ -181,9 +184,7 @@ contract DeputyGuardianModule_SetAnchorState_Test is DeputyGuardianModule_TestIn function test_setAnchorState_succeeds() external { IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); vm.mockCall( - address(asr), - abi.encodeWithSelector(IAnchorStateRegistry.setAnchorState.selector, IFaultDisputeGame(address(0))), - "" + address(asr), abi.encodeCall(IAnchorStateRegistry.setAnchorState, (IFaultDisputeGame(address(0)))), "" ); vm.expectEmit(address(safeInstance.safe)); emit ExecutionFromModuleSuccess(address(deputyGuardianModule)); @@ -223,7 +224,7 @@ contract DeputyGuardianModule_BlacklistDisputeGame_TestFail is DeputyGuardianMod function test_blacklistDisputeGame_targetReverts_reverts() external { vm.mockCallRevert( address(optimismPortal2), - abi.encodeWithSelector(optimismPortal2.blacklistDisputeGame.selector), + abi.encodePacked(optimismPortal2.blacklistDisputeGame.selector), "OptimismPortal2: blacklistDisputeGame reverted" ); @@ -256,7 +257,13 @@ contract DeputyGuardianModule_setRespectedGameType_Test is DeputyGuardianModule_ contract DeputyGuardianModule_setRespectedGameType_TestFail is DeputyGuardianModule_TestInit { /// @dev Tests that `setRespectedGameType` when called by a non deputy guardian. function testFuzz_setRespectedGameType_notDeputyGuardian_reverts(GameType _gameType) external { - vm.assume(GameType.unwrap(optimismPortal2.respectedGameType()) != GameType.unwrap(_gameType)); + // Change the game type if it's the same to avoid test rejections. + if (GameType.unwrap(optimismPortal2.respectedGameType()) == GameType.unwrap(_gameType)) { + unchecked { + _gameType = GameType.wrap(GameType.unwrap(_gameType) + 1); + } + } + vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); deputyGuardianModule.setRespectedGameType(optimismPortal2, _gameType); assertNotEq(GameType.unwrap(optimismPortal2.respectedGameType()), GameType.unwrap(_gameType)); @@ -266,7 +273,7 @@ contract DeputyGuardianModule_setRespectedGameType_TestFail is DeputyGuardianMod function test_setRespectedGameType_targetReverts_reverts() external { vm.mockCallRevert( address(optimismPortal2), - abi.encodeWithSelector(optimismPortal2.setRespectedGameType.selector), + abi.encodePacked(optimismPortal2.setRespectedGameType.selector), "OptimismPortal2: setRespectedGameType reverted" ); @@ -287,8 +294,8 @@ contract DeputyGuardianModule_NoPortalCollisions_Test is DeputyGuardianModule_Te excludes[0] = "src/dispute/lib/*"; excludes[1] = "src/L1/OptimismPortal2.sol"; excludes[2] = "src/L1/OptimismPortalInterop.sol"; - excludes[3] = "src/L1/interfaces/IOptimismPortal2.sol"; - excludes[4] = "src/L1/interfaces/IOptimismPortalInterop.sol"; + excludes[3] = "interfaces/L1/IOptimismPortal2.sol"; + excludes[4] = "interfaces/L1/IOptimismPortalInterop.sol"; Abi[] memory abis = ForgeArtifacts.getContractFunctionAbis("src/{L1,dispute,universal}", excludes); for (uint256 i; i < abis.length; i++) { for (uint256 j; j < abis[i].entries.length; j++) { diff --git a/packages/contracts-bedrock/test/safe/LivenessGuard.t.sol b/packages/contracts-bedrock/test/safe/LivenessGuard.t.sol index f1386a6608a22..90a0ecd9f1fc3 100644 --- a/packages/contracts-bedrock/test/safe/LivenessGuard.t.sol +++ b/packages/contracts-bedrock/test/safe/LivenessGuard.t.sol @@ -68,7 +68,7 @@ contract LivenessGuard_Getters_Test is LivenessGuard_TestInit { contract LivenessGuard_CheckTx_TestFails is LivenessGuard_TestInit { /// @dev Tests that the checkTransaction function reverts if the caller is not the Safe - function test_checkTransaction_callerIsNotSafe_revert() external { + function test_checkTransaction_callerIsNotSafe_reverts() external { vm.expectRevert("LivenessGuard: only Safe can call this function"); livenessGuard.checkTransaction({ _to: address(0), @@ -110,7 +110,7 @@ contract LivenessGuard_CheckTx_Test is LivenessGuard_TestInit { vm.expectEmit(address(livenessGuard)); emit OwnerRecorded(signers[i]); } - vm.expectCall(address(safeInstance.safe), abi.encodeWithSignature("nonce()")); + vm.expectCall(address(safeInstance.safe), abi.encodeCall(safeInstance.safe.nonce, ())); vm.expectCall(address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ())); safeInstance.execTransaction({ to: address(1111), value: 0, data: hex"abba" }); for (uint256 i; i < safeInstance.threshold; i++) { @@ -123,7 +123,7 @@ contract LivenessGuard_CheckTx_Test is LivenessGuard_TestInit { contract LivenessGuard_CheckAfterExecution_TestFails is LivenessGuard_TestInit { /// @dev Tests that the checkAfterExecution function reverts if the caller is not the Safe - function test_checkAfterExecution_callerIsNotSafe_revert() external { + function test_checkAfterExecution_callerIsNotSafe_reverts() external { vm.expectRevert("LivenessGuard: only Safe can call this function"); livenessGuard.checkAfterExecution(bytes32(0), false); } @@ -228,7 +228,7 @@ contract LivenessGuard_FuzzOwnerManagement_Test is StdCheats, StdUtils, Liveness mapping(address => uint256) privateKeys; /// @dev Tests that the guard correctly manages the lastLive mapping when owners are added, removed, or swapped - function testFuzz_OwnerManagement_works( + function testFuzz_ownerManagement_works( uint256 initialOwners, uint256 threshold, OwnerChange[] memory changes diff --git a/packages/contracts-bedrock/test/safe/LivenessModule.t.sol b/packages/contracts-bedrock/test/safe/LivenessModule.t.sol index e101f59ca5a41..5a6a30950227e 100644 --- a/packages/contracts-bedrock/test/safe/LivenessModule.t.sol +++ b/packages/contracts-bedrock/test/safe/LivenessModule.t.sol @@ -488,7 +488,7 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { } /// @dev Tests if removing owners works correctly for various safe configurations and numbeers of live owners - function testFuzz_removeOwners( + function testFuzz_removeOwners_works( uint256 _numOwners, uint256 _minOwners, uint256 _numLiveOwners, diff --git a/packages/contracts-bedrock/test/safe/SafeSigners.t.sol b/packages/contracts-bedrock/test/safe/SafeSigners.t.sol index 9cfa918698993..cb9737a3e9751 100644 --- a/packages/contracts-bedrock/test/safe/SafeSigners.t.sol +++ b/packages/contracts-bedrock/test/safe/SafeSigners.t.sol @@ -75,6 +75,7 @@ contract SafeSigners_Test is Test, SafeTestTools { contractSigs++; address addr = SafeTestLib.decodeSmartContractWalletAsAddress(pks[i]); r = bytes32(uint256(uint160(addr))); + // nosemgrep: sol-style-use-abi-encodecall vm.mockCall( addr, abi.encodeWithSignature("isValidSignature(bytes,bytes)"), abi.encode(EIP1271_MAGIC_VALUE) ); diff --git a/packages/contracts-bedrock/test/setup/Bridge_Initializer.sol b/packages/contracts-bedrock/test/setup/Bridge_Initializer.sol deleted file mode 100644 index 6b931712935ea..0000000000000 --- a/packages/contracts-bedrock/test/setup/Bridge_Initializer.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { CommonTest } from "test/setup/CommonTest.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; -import { LegacyMintableERC20 } from "src/legacy/LegacyMintableERC20.sol"; - -/// @title Bridge_Initializer -/// @dev This contract extends the CommonTest contract with token deployments -/// meant to be used with the bridge contracts. -contract Bridge_Initializer is CommonTest { - ERC20 L1Token; - ERC20 BadL1Token; - OptimismMintableERC20 L2Token; - LegacyMintableERC20 LegacyL2Token; - ERC20 NativeL2Token; - ERC20 BadL2Token; - OptimismMintableERC20 RemoteL1Token; - - function setUp() public virtual override { - super.setUp(); - - L1Token = new ERC20("Native L1 Token", "L1T"); - - LegacyL2Token = new LegacyMintableERC20({ - _l2Bridge: address(l2StandardBridge), - _l1Token: address(L1Token), - _name: string.concat("LegacyL2-", L1Token.name()), - _symbol: string.concat("LegacyL2-", L1Token.symbol()) - }); - vm.label(address(LegacyL2Token), "LegacyMintableERC20"); - - // Deploy the L2 ERC20 now - L2Token = OptimismMintableERC20( - l2OptimismMintableERC20Factory.createStandardL2Token( - address(L1Token), - string(abi.encodePacked("L2-", L1Token.name())), - string(abi.encodePacked("L2-", L1Token.symbol())) - ) - ); - - BadL2Token = OptimismMintableERC20( - l2OptimismMintableERC20Factory.createStandardL2Token( - address(1), - string(abi.encodePacked("L2-", L1Token.name())), - string(abi.encodePacked("L2-", L1Token.symbol())) - ) - ); - - NativeL2Token = new ERC20("Native L2 Token", "L2T"); - - RemoteL1Token = OptimismMintableERC20( - l1OptimismMintableERC20Factory.createStandardL2Token( - address(NativeL2Token), - string(abi.encodePacked("L1-", NativeL2Token.name())), - string(abi.encodePacked("L1-", NativeL2Token.symbol())) - ) - ); - - BadL1Token = OptimismMintableERC20( - l1OptimismMintableERC20Factory.createStandardL2Token( - address(1), - string(abi.encodePacked("L1-", NativeL2Token.name())), - string(abi.encodePacked("L1-", NativeL2Token.symbol())) - ) - ); - } -} diff --git a/packages/contracts-bedrock/test/setup/CommonTest.sol b/packages/contracts-bedrock/test/setup/CommonTest.sol index e731b3a5ba76e..de54472248beb 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -6,7 +6,9 @@ import { Setup } from "test/setup/Setup.sol"; import { Events } from "test/setup/Events.sol"; import { FFIInterface } from "test/setup/FFIInterface.sol"; import { Constants } from "src/libraries/Constants.sol"; -import "scripts/deploy/DeployConfig.s.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; +import { LegacyMintableERC20 } from "src/legacy/LegacyMintableERC20.sol"; /// @title CommonTest /// @dev An extenstion to `Test` that sets up the optimism smart contracts. @@ -19,9 +21,19 @@ contract CommonTest is Test, Setup, Events { FFIInterface constant ffi = FFIInterface(address(uint160(uint256(keccak256(abi.encode("optimism.ffi")))))); bool useAltDAOverride; - bool useFaultProofs; + bool useLegacyContracts; address customGasToken; bool useInteropOverride; + bool useSoulGasToken; + bool isSoulBackedByNative; + + ERC20 L1Token; + ERC20 BadL1Token; + OptimismMintableERC20 L2Token; + LegacyMintableERC20 LegacyL2Token; + ERC20 NativeL2Token; + ERC20 BadL2Token; + OptimismMintableERC20 RemoteL1Token; function setUp() public virtual override { alice = makeAddr("alice"); @@ -35,7 +47,8 @@ contract CommonTest is Test, Setup, Events { if (useAltDAOverride) { deploy.cfg().setUseAltDA(true); } - if (useFaultProofs) { + // We default to fault proofs unless explicitly disabled by useLegacyContracts + if (!useLegacyContracts) { deploy.cfg().setUseFaultProofs(true); } if (customGasToken != address(0)) { @@ -44,6 +57,12 @@ contract CommonTest is Test, Setup, Events { if (useInteropOverride) { deploy.cfg().setUseInterop(true); } + if (useSoulGasToken) { + deploy.cfg().setUseSoulGasToken(true); + if (isSoulBackedByNative) { + deploy.cfg().setIsSoulBackedByNative(true); + } + } vm.etch(address(ffi), vm.getDeployedCode("FFIInterface.sol:FFIInterface")); vm.label(address(ffi), "FFIInterface"); @@ -64,6 +83,56 @@ contract CommonTest is Test, Setup, Events { Setup.L1(); // Deploy L2 Setup.L2(); + + // Call bridge initializer setup function + bridgeInitializerSetUp(); + } + + function bridgeInitializerSetUp() public { + L1Token = new ERC20("Native L1 Token", "L1T"); + + LegacyL2Token = new LegacyMintableERC20({ + _l2Bridge: address(l2StandardBridge), + _l1Token: address(L1Token), + _name: string.concat("LegacyL2-", L1Token.name()), + _symbol: string.concat("LegacyL2-", L1Token.symbol()) + }); + vm.label(address(LegacyL2Token), "LegacyMintableERC20"); + + // Deploy the L2 ERC20 now + L2Token = OptimismMintableERC20( + l2OptimismMintableERC20Factory.createStandardL2Token( + address(L1Token), + string(abi.encodePacked("L2-", L1Token.name())), + string(abi.encodePacked("L2-", L1Token.symbol())) + ) + ); + + BadL2Token = OptimismMintableERC20( + l2OptimismMintableERC20Factory.createStandardL2Token( + address(1), + string(abi.encodePacked("L2-", L1Token.name())), + string(abi.encodePacked("L2-", L1Token.symbol())) + ) + ); + + NativeL2Token = new ERC20("Native L2 Token", "L2T"); + + RemoteL1Token = OptimismMintableERC20( + l1OptimismMintableERC20Factory.createStandardL2Token( + address(NativeL2Token), + string(abi.encodePacked("L1-", NativeL2Token.name())), + string(abi.encodePacked("L1-", NativeL2Token.symbol())) + ) + ); + + BadL1Token = OptimismMintableERC20( + l1OptimismMintableERC20Factory.createStandardL2Token( + address(1), + string(abi.encodePacked("L1-", NativeL2Token.name())), + string(abi.encodePacked("L1-", NativeL2Token.symbol())) + ) + ); } /// @dev Helper function that wraps `TransactionDeposited` event. @@ -109,14 +178,14 @@ contract CommonTest is Test, Setup, Events { l2OutputOracle.proposeL2Output(proposedOutput2, nextBlockNumber, 0, 0); } - function enableFaultProofs() public { + function enableLegacyContracts() public { // Check if the system has already been deployed, based off of the heuristic that alice and bob have not been // set by the `setUp` function yet. if (!(alice == address(0) && bob == address(0))) { revert("CommonTest: Cannot enable fault proofs after deployment. Consider overriding `setUp`."); } - useFaultProofs = true; + useLegacyContracts = true; } function enableAltDA() public { @@ -147,7 +216,17 @@ contract CommonTest is Test, Setup, Events { revert("CommonTest: Cannot enable interop after deployment. Consider overriding `setUp`."); } - useFaultProofs = true; useInteropOverride = true; } + + function enableSoulGasToken() public { + // Check if the system has already been deployed, based off of the heuristic that alice and bob have not been + // set by the `setUp` function yet. + if (!(alice == address(0) && bob == address(0))) { + revert("CommonTest: Cannot enable interop after deployment. Consider overriding `setUp`."); + } + + useSoulGasToken = true; + isSoulBackedByNative = true; + } } diff --git a/packages/contracts-bedrock/test/setup/DeployVariations.t.sol b/packages/contracts-bedrock/test/setup/DeployVariations.t.sol index 97141fe86ee6d..94628067e119f 100644 --- a/packages/contracts-bedrock/test/setup/DeployVariations.t.sol +++ b/packages/contracts-bedrock/test/setup/DeployVariations.t.sol @@ -22,20 +22,18 @@ contract DeployVariations_Test is CommonTest { } } - /// forge-config: ciheavy.fuzz.runs = 512 /// @dev It should be possible to enable Fault Proofs with any mix of CGT and Alt-DA. - function testFuzz_enableFaultProofs(bool _enableCGT, bool _enableAltDa) public virtual { + function testFuzz_enableFaultProofs_succeeds(bool _enableCGT, bool _enableAltDa) public virtual { enableAddOns(_enableCGT, _enableAltDa); - super.enableFaultProofs(); + super.setUp(); } - /// forge-config: ciheavy.fuzz.runs = 512 /// @dev It should be possible to enable Fault Proofs and Interop with any mix of CGT and Alt-DA. - function test_enableInteropAndFaultProofs(bool _enableCGT, bool _enableAltDa) public virtual { + function test_enableInteropAndFaultProofs_succeeds(bool _enableCGT, bool _enableAltDa) public virtual { enableAddOns(_enableCGT, _enableAltDa); super.enableInterop(); - super.enableFaultProofs(); + super.setUp(); } } diff --git a/packages/contracts-bedrock/test/setup/Events.sol b/packages/contracts-bedrock/test/setup/Events.sol index 7f8017bfb2c1d..7056f0cbdd6b0 100644 --- a/packages/contracts-bedrock/test/setup/Events.sol +++ b/packages/contracts-bedrock/test/setup/Events.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import "src/dispute/lib/Types.sol"; import { Types } from "src/libraries/Types.sol"; @@ -104,4 +104,6 @@ contract Events { event Paused(string identifier); event Unpaused(); + + event BalanceChanged(address account, uint256 balance); } diff --git a/packages/contracts-bedrock/test/setup/FFIInterface.sol b/packages/contracts-bedrock/test/setup/FFIInterface.sol index 727402a37c2c1..c1e1612da8e6b 100644 --- a/packages/contracts-bedrock/test/setup/FFIInterface.sol +++ b/packages/contracts-bedrock/test/setup/FFIInterface.sol @@ -317,6 +317,90 @@ contract FFIInterface { return (memRoot, proof); } + function getCannonMemory64Proof(uint64 addr, uint64 value) external returns (bytes32, bytes memory) { + string[] memory cmds = new string[](5); + cmds[0] = "scripts/go-ffi/go-ffi-cannon64"; + cmds[1] = "diff"; + cmds[2] = "cannonMemoryProof"; + cmds[3] = vm.toString(addr); + cmds[4] = vm.toString(value); + bytes memory result = Process.run(cmds); + (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); + return (memRoot, proof); + } + + function getCannonMemory64Proof( + uint64 addr0, + uint64 value0, + uint64 addr1, + uint64 value1 + ) + external + returns (bytes32, bytes memory) + { + string[] memory cmds = new string[](7); + cmds[0] = "scripts/go-ffi/go-ffi-cannon64"; + cmds[1] = "diff"; + cmds[2] = "cannonMemoryProof"; + cmds[3] = vm.toString(addr0); + cmds[4] = vm.toString(value0); + cmds[5] = vm.toString(addr1); + cmds[6] = vm.toString(value1); + bytes memory result = Process.run(cmds); + (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); + return (memRoot, proof); + } + + function getCannonMemory64Proof( + uint64 addr0, + uint64 value0, + uint64 addr1, + uint64 value1, + uint64 memAddr2, + uint64 memVal2 + ) + external + returns (bytes32, bytes memory) + { + string[] memory cmds = new string[](9); + cmds[0] = "scripts/go-ffi/go-ffi-cannon64"; + cmds[1] = "diff"; + cmds[2] = "cannonMemoryProof"; + cmds[3] = vm.toString(addr0); + cmds[4] = vm.toString(value0); + cmds[5] = vm.toString(addr1); + cmds[6] = vm.toString(value1); + cmds[7] = vm.toString(memAddr2); + cmds[8] = vm.toString(memVal2); + bytes memory result = Process.run(cmds); + (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); + return (memRoot, proof); + } + + function getCannonMemory64Proof2( + uint64 addr0, + uint64 value0, + uint64 addr1, + uint64 value1, + uint64 memAddrForProof + ) + external + returns (bytes32, bytes memory) + { + string[] memory cmds = new string[](8); + cmds[0] = "scripts/go-ffi/go-ffi-cannon64"; + cmds[1] = "diff"; + cmds[2] = "cannonMemoryProof2"; + cmds[3] = vm.toString(addr0); + cmds[4] = vm.toString(value0); + cmds[5] = vm.toString(addr1); + cmds[6] = vm.toString(value1); + cmds[7] = vm.toString(memAddrForProof); + bytes memory result = Process.run(cmds); + (bytes32 memRoot, bytes memory proof) = abi.decode(result, (bytes32, bytes)); + return (memRoot, proof); + } + function encodeScalarEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external returns (bytes32) { string[] memory cmds = new string[](5); cmds[0] = "scripts/go-ffi/go-ffi"; diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 32fe86b66b9f6..e93b7f2c45e29 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -17,37 +17,39 @@ import { Preinstalls } from "src/libraries/Preinstalls.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; // Interfaces -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; -import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; -import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; -import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; -import { IOptimismMintableERC721Factory } from "src/universal/interfaces/IOptimismMintableERC721Factory.sol"; -import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IL2CrossDomainMessenger } from "src/L2/interfaces/IL2CrossDomainMessenger.sol"; -import { IL2StandardBridgeInterop } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; -import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol"; -import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; -import { IBaseFeeVault } from "src/L2/interfaces/IBaseFeeVault.sol"; -import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol"; -import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol"; -import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol"; -import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; -import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; -import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; -import { IWETH } from "src/universal/interfaces/IWETH.sol"; -import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; -import { ILegacyMessagePasser } from "src/legacy/interfaces/ILegacyMessagePasser.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; +import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; +import { IOptimismMintableERC721Factory } from "interfaces/universal/IOptimismMintableERC721Factory.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IL2CrossDomainMessenger } from "interfaces/L2/IL2CrossDomainMessenger.sol"; +import { IL2StandardBridgeInterop } from "interfaces/L2/IL2StandardBridgeInterop.sol"; +import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; +import { IL2ERC721Bridge } from "interfaces/L2/IL2ERC721Bridge.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IOptimismSuperchainERC20Factory } from "interfaces/L2/IOptimismSuperchainERC20Factory.sol"; +import { IBaseFeeVault } from "interfaces/L2/IBaseFeeVault.sol"; +import { ISequencerFeeVault } from "interfaces/L2/ISequencerFeeVault.sol"; +import { IL1FeeVault } from "interfaces/L2/IL1FeeVault.sol"; +import { IGasPriceOracle } from "interfaces/L2/IGasPriceOracle.sol"; +import { IL1Block } from "interfaces/L2/IL1Block.sol"; +import { ISuperchainWETH } from "interfaces/L2/ISuperchainWETH.sol"; +import { IETHLiquidity } from "interfaces/L2/IETHLiquidity.sol"; +import { IWETH98 } from "interfaces/universal/IWETH98.sol"; +import { IGovernanceToken } from "interfaces/governance/IGovernanceToken.sol"; +import { ILegacyMessagePasser } from "interfaces/legacy/ILegacyMessagePasser.sol"; +import { ISuperchainTokenBridge } from "interfaces/L2/ISuperchainTokenBridge.sol"; +import { ISoulGasToken } from "interfaces/L2/ISoulGasToken.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -104,13 +106,13 @@ contract Setup { IL1Block l1Block = IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES); IGovernanceToken governanceToken = IGovernanceToken(Predeploys.GOVERNANCE_TOKEN); ILegacyMessagePasser legacyMessagePasser = ILegacyMessagePasser(Predeploys.LEGACY_MESSAGE_PASSER); - IWETH weth = IWETH(payable(Predeploys.WETH)); + IWETH98 weth = IWETH98(payable(Predeploys.WETH)); ISuperchainWETH superchainWeth = ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY); - - // TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas - IOptimismERC20Factory l2OptimismSuperchainERC20Factory = - IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + ISuperchainTokenBridge superchainTokenBridge = ISuperchainTokenBridge(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory = + IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + ISoulGasToken soulGasToken = ISoulGasToken(Predeploys.SOUL_GAS_TOKEN); /// @dev Deploys the Deploy contract without including its bytecode in the bytecode /// of this contract by fetching the bytecode dynamically using `vm.getCode()`. @@ -236,6 +238,8 @@ contract Setup { labelPredeploy(Predeploys.ETH_LIQUIDITY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + labelPredeploy(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); + labelPredeploy(Predeploys.SOUL_GAS_TOKEN); // L2 Preinstalls labelPreinstall(Preinstalls.MultiCall3); diff --git a/packages/contracts-bedrock/slither.config.json b/packages/contracts-bedrock/test/slither/slither.config.json similarity index 100% rename from packages/contracts-bedrock/slither.config.json rename to packages/contracts-bedrock/test/slither/slither.config.json diff --git a/packages/contracts-bedrock/slither.db.json b/packages/contracts-bedrock/test/slither/slither.db.json similarity index 100% rename from packages/contracts-bedrock/slither.db.json rename to packages/contracts-bedrock/test/slither/slither.db.json diff --git a/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol b/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol index a129736c771bb..c27b493344b32 100644 --- a/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol +++ b/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol @@ -5,16 +5,16 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; import { Vm } from "forge-std/Vm.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; // Libraries import { Types } from "src/libraries/Types.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; -import { L1BlockInterop } from "src/L2/L1BlockInterop.sol"; +import { IL1BlockInterop } from "interfaces/L2/IL1BlockInterop.sol"; import { Encoding } from "src/libraries/Encoding.sol"; // Interfaces -import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Free function for setting the prevBaseFee param in the OptimismPortal. function setPrevBaseFee(Vm _vm, address _op, uint128 _prevBaseFee) { @@ -47,6 +47,7 @@ contract GasBenchMark_OptimismPortal is CommonTest { // Use a constructor to set the storage vars above, so as to minimize the number of ffi calls. constructor() { + super.enableLegacyContracts(); super.setUp(); _defaultTx = Types.WithdrawalTransaction({ nonce: 0, @@ -108,7 +109,7 @@ contract GasBenchMark_OptimismPortal is CommonTest { } } -contract GasBenchMark_L1CrossDomainMessenger is Bridge_Initializer { +contract GasBenchMark_L1CrossDomainMessenger is CommonTest { function test_sendMessage_benchmark_0() external { vm.pauseGasMetering(); setPrevBaseFee(vm, address(optimismPortal), 1 gwei); @@ -130,7 +131,7 @@ contract GasBenchMark_L1CrossDomainMessenger is Bridge_Initializer { } } -contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { +contract GasBenchMark_L1StandardBridge_Deposit is CommonTest { function setUp() public virtual override { super.setUp(); deal(address(L1Token), alice, 100000, true); @@ -179,13 +180,13 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { } } -contract GasBenchMark_L1StandardBridge_Finalize is Bridge_Initializer { +contract GasBenchMark_L1StandardBridge_Finalize is CommonTest { function setUp() public virtual override { super.setUp(); deal(address(L1Token), address(l1StandardBridge), 100, true); vm.mockCall( address(l1StandardBridge.messenger()), - abi.encodeWithSelector(ICrossDomainMessenger.xDomainMessageSender.selector), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) ); vm.startPrank(address(l1StandardBridge.messenger())); @@ -204,6 +205,7 @@ contract GasBenchMark_L2OutputOracle is CommonTest { uint256 nextBlockNumber; function setUp() public override { + super.enableLegacyContracts(); super.setUp(); nextBlockNumber = l2OutputOracle.nextBlockNumber(); warpToProposeTime(nextBlockNumber); @@ -255,11 +257,16 @@ contract GasBenchMark_L1Block_SetValuesEcotone_Warm is GasBenchMark_L1Block { } contract GasBenchMark_L1BlockInterop is GasBenchMark_L1Block { - L1BlockInterop l1BlockInterop; + IL1BlockInterop l1BlockInterop; function setUp() public virtual override { super.setUp(); - l1BlockInterop = new L1BlockInterop(); + l1BlockInterop = IL1BlockInterop( + DeployUtils.create1({ + _name: "L1BlockInterop", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1BlockInterop.__constructor__, ())) + }) + ); setValuesCalldata = Encoding.encodeSetL1BlockValuesInterop( type(uint32).max, type(uint32).max, @@ -294,7 +301,7 @@ contract GasBenchMark_L1BlockInterop_DepositsComplete is GasBenchMark_L1BlockInt function test_depositsComplete_benchmark() external { SafeCall.call({ _target: address(l1BlockInterop), - _calldata: abi.encodeWithSelector(l1BlockInterop.depositsComplete.selector) + _calldata: abi.encodeCall(IL1BlockInterop.depositsComplete, ()) }); } } @@ -309,7 +316,7 @@ contract GasBenchMark_L1BlockInterop_DepositsComplete_Warm is GasBenchMark_L1Blo function test_depositsComplete_benchmark() external { SafeCall.call({ _target: address(l1BlockInterop), - _calldata: abi.encodeWithSelector(l1BlockInterop.depositsComplete.selector) + _calldata: abi.encodeCall(l1BlockInterop.depositsComplete, ()) }); } } diff --git a/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol index dddb09d41239c..12cc2a8c53b2e 100644 --- a/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol @@ -3,18 +3,18 @@ pragma solidity 0.8.15; // Testing utilities import { Test } from "forge-std/Test.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Encoding } from "src/libraries/Encoding.sol"; -import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; // CrossDomainMessenger_Test is for testing functionality which is common to both the L1 and L2 // CrossDomainMessenger contracts. For simplicity, we use the L1 Messenger as the test contract. -contract CrossDomainMessenger_BaseGas_Test is Bridge_Initializer { +contract CrossDomainMessenger_BaseGas_Test is CommonTest { /// @dev Ensure that baseGas passes for the max value of _minGasLimit, /// this is about 4 Billion. function test_baseGas_succeeds() external view { @@ -99,7 +99,7 @@ contract ExternalRelay is Test { /// @notice Helper function to get the callData for an `externalCallWithMinGas function getCallData() public pure returns (bytes memory) { - return abi.encodeWithSelector(ExternalRelay.externalCallWithMinGas.selector); + return abi.encodeCall(ExternalRelay.externalCallWithMinGas, ()); } /// @notice Helper function to set the fuzzed sender @@ -110,7 +110,7 @@ contract ExternalRelay is Test { /// @title CrossDomainMessenger_RelayMessage_Test /// @notice Fuzz tests re-entrancy into the CrossDomainMessenger relayMessage function. -contract CrossDomainMessenger_RelayMessage_Test is Bridge_Initializer { +contract CrossDomainMessenger_RelayMessage_Test is CommonTest { // Storage slot of the l2Sender uint256 constant senderSlotIndex = 50; diff --git a/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol b/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol index 1e84ce2958cf3..4925c801885d6 100644 --- a/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol +++ b/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; -import { ILegacyMintableERC20, IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; +import { IOptimismMintableERC20 } from "interfaces/universal/IOptimismMintableERC20.sol"; +import { ILegacyMintableERC20 } from "interfaces/universal/ILegacyMintableERC20.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -contract OptimismMintableERC20_Test is Bridge_Initializer { +contract OptimismMintableERC20_Test is CommonTest { event Mint(address indexed account, uint256 amount); event Burn(address indexed account, uint256 amount); @@ -46,11 +47,11 @@ contract OptimismMintableERC20_Test is Bridge_Initializer { assertEq(L2Token.balanceOf(alice), 100); } - function test_allowance_permit2_max() external view { + function test_allowance_permit2Max_works() external view { assertEq(L2Token.allowance(alice, L2Token.PERMIT2()), type(uint256).max); } - function test_permit2_transferFrom() external { + function test_permit2_transferFrom_succeeds() external { vm.prank(address(l2StandardBridge)); L2Token.mint(alice, 100); diff --git a/packages/contracts-bedrock/test/universal/OptimismMintableERC20Factory.t.sol b/packages/contracts-bedrock/test/universal/OptimismMintableERC20Factory.t.sol index cba5fc8290862..867c11b3884e9 100644 --- a/packages/contracts-bedrock/test/universal/OptimismMintableERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/universal/OptimismMintableERC20Factory.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; // Testing -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; import { NextImpl } from "test/mocks/NextImpl.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; @@ -11,10 +11,10 @@ import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; // Interfaces -import { IProxy } from "src/universal/interfaces/IProxy.sol"; -import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; -contract OptimismMintableTokenFactory_Test is Bridge_Initializer { +contract OptimismMintableTokenFactory_Test is CommonTest { event StandardL2TokenCreated(address indexed remoteToken, address indexed localToken); event OptimismMintableERC20Created(address indexed localToken, address indexed remoteToken, address deployer); @@ -42,7 +42,7 @@ contract OptimismMintableTokenFactory_Test is Bridge_Initializer { vm.startPrank(EIP1967Helper.getAdmin(address(proxy))); // Reviewer note: the NextImpl() still uses reinitializer. If we want to remove that, we'll need to use a // two step upgrade with the Storage lib. - proxy.upgradeToAndCall(address(nextImpl), abi.encodeWithSelector(NextImpl.initialize.selector, 2)); + proxy.upgradeToAndCall(address(nextImpl), abi.encodeCall(NextImpl.initialize, (2))); assertEq(proxy.implementation(), address(nextImpl)); // Verify that the NextImpl contract initialized its values according as expected diff --git a/packages/contracts-bedrock/test/universal/OptimismMintableERC721.t.sol b/packages/contracts-bedrock/test/universal/OptimismMintableERC721.t.sol index daea00064cf44..dfe2234e0b644 100644 --- a/packages/contracts-bedrock/test/universal/OptimismMintableERC721.t.sol +++ b/packages/contracts-bedrock/test/universal/OptimismMintableERC721.t.sol @@ -5,10 +5,10 @@ import { ERC721, IERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol import { IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; import { OptimismMintableERC721, IOptimismMintableERC721 } from "src/universal/OptimismMintableERC721.sol"; -contract OptimismMintableERC721_Test is Bridge_Initializer { +contract OptimismMintableERC721_Test is CommonTest { ERC721 internal L1NFT; OptimismMintableERC721 internal L2NFT; @@ -30,6 +30,7 @@ contract OptimismMintableERC721_Test is Bridge_Initializer { vm.label(address(L2NFT), "L2ERC721Token"); } + /// @notice Tests that the constructor works as expected. function test_constructor_succeeds() external view { assertEq(L2NFT.name(), "L2NFT"); assertEq(L2NFT.symbol(), "L2T"); @@ -41,6 +42,24 @@ contract OptimismMintableERC721_Test is Bridge_Initializer { assertEq(L2NFT.REMOTE_CHAIN_ID(), 1); } + /// @notice Tests that the bridge cannot be address(0) at construction time. + function test_constructor_bridgeAsAddress0_reverts() external { + vm.expectRevert("OptimismMintableERC721: bridge cannot be address(0)"); + L2NFT = new OptimismMintableERC721(address(0), 1, address(L1NFT), "L2NFT", "L2T"); + } + + /// @notice Tests that the remote chain ID cannot be zero at construction time. + function test_constructor_remoteChainId0_reverts() external { + vm.expectRevert("OptimismMintableERC721: remote chain id cannot be zero"); + L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), 0, address(L1NFT), "L2NFT", "L2T"); + } + + /// @notice Tests that the remote token cannot be address(0) at construction time. + function test_constructor_remoteTokenAsAddress0_reverts() external { + vm.expectRevert("OptimismMintableERC721: remote token cannot be address(0)"); + L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), 1, address(0), "L2NFT", "L2T"); + } + /// @notice Ensure that the contract supports the expected interfaces. function test_supportsInterfaces_succeeds() external view { // Checks if the contract supports the IOptimismMintableERC721 interface. diff --git a/packages/contracts-bedrock/test/universal/OptimismMintableERC721Factory.t.sol b/packages/contracts-bedrock/test/universal/OptimismMintableERC721Factory.t.sol index ef9019eafa04f..91340001c5e95 100644 --- a/packages/contracts-bedrock/test/universal/OptimismMintableERC721Factory.t.sol +++ b/packages/contracts-bedrock/test/universal/OptimismMintableERC721Factory.t.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; import { OptimismMintableERC721 } from "src/universal/OptimismMintableERC721.sol"; import { OptimismMintableERC721Factory } from "src/universal/OptimismMintableERC721Factory.sol"; -contract OptimismMintableERC721Factory_Test is Bridge_Initializer { +contract OptimismMintableERC721Factory_Test is CommonTest { event OptimismMintableERC721Created(address indexed localToken, address indexed remoteToken, address deployer); function test_constructor_succeeds() external view { diff --git a/packages/contracts-bedrock/test/universal/Proxy.t.sol b/packages/contracts-bedrock/test/universal/Proxy.t.sol index 66c3f1d242684..437fabfe67141 100644 --- a/packages/contracts-bedrock/test/universal/Proxy.t.sol +++ b/packages/contracts-bedrock/test/universal/Proxy.t.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; -import { Proxy } from "src/universal/Proxy.sol"; import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract SimpleStorage { mapping(uint256 => uint256) internal store; @@ -19,7 +20,7 @@ contract SimpleStorage { contract Clasher { function upgradeTo(address) external pure { - revert("upgradeTo"); + revert("Clasher: upgradeTo"); } } @@ -33,13 +34,18 @@ contract Proxy_Test is Test { bytes32 internal constant OWNER_KEY = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1); - Proxy proxy; + IProxy proxy; SimpleStorage simpleStorage; function setUp() external { // Deploy a proxy and simple storage contract as // the implementation - proxy = new Proxy(alice); + proxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (alice))) + }) + ); simpleStorage = new SimpleStorage(); vm.prank(alice); @@ -154,7 +160,7 @@ contract Proxy_Test is Test { vm.expectEmit(true, true, true, true); emit Upgraded(address(simpleStorage)); vm.prank(alice); - proxy.upgradeToAndCall(address(simpleStorage), abi.encodeWithSelector(simpleStorage.set.selector, 1, 1)); + proxy.upgradeToAndCall(address(simpleStorage), abi.encodeCall(SimpleStorage.set, (1, 1))); // The call should have impacted the state uint256 result = SimpleStorage(address(proxy)).get(1); @@ -187,7 +193,7 @@ contract Proxy_Test is Test { // The attempt to `upgradeToAndCall` // should revert when it is not called by the owner. vm.expectRevert(bytes("")); - proxy.upgradeToAndCall(address(simpleStorage), abi.encodeWithSelector(simpleStorage.set.selector, 1, 1)); + proxy.upgradeToAndCall(address(simpleStorage), abi.encodeCall(simpleStorage.set, (1, 1))); } function test_upgradeToAndCall_isPayable_succeeds() external { @@ -196,9 +202,7 @@ contract Proxy_Test is Test { // Set the implementation and call and send // value. vm.prank(alice); - proxy.upgradeToAndCall{ value: 1 ether }( - address(simpleStorage), abi.encodeWithSelector(simpleStorage.set.selector, 1, 1) - ); + proxy.upgradeToAndCall{ value: 1 ether }(address(simpleStorage), abi.encodeCall(simpleStorage.set, (1, 1))); // The implementation address should be correct vm.prank(alice); @@ -228,7 +232,7 @@ contract Proxy_Test is Test { // not as the owner so that the call passes through. // The implementation will revert so we can be // sure that the call passed through. - vm.expectRevert(bytes("upgradeTo")); + vm.expectRevert(bytes("Clasher: upgradeTo")); proxy.upgradeTo(address(0)); { @@ -259,7 +263,8 @@ contract Proxy_Test is Test { (bool success, bytes memory returndata) = address(proxy).call(hex""); assertEq(success, false); - bytes memory err = abi.encodeWithSignature("Error(string)", "Proxy: implementation not initialized"); + bytes memory err = abi.encodeWithSignature("Error(string)", "Proxy: implementation not initialized"); // nosemgrep: + // sol-style-use-abi-encodecall assertEq(returndata, err); } diff --git a/packages/contracts-bedrock/test/universal/ProxyAdmin.t.sol b/packages/contracts-bedrock/test/universal/ProxyAdmin.t.sol index e212644c9d50d..b1b6fa92a2097 100644 --- a/packages/contracts-bedrock/test/universal/ProxyAdmin.t.sol +++ b/packages/contracts-bedrock/test/universal/ProxyAdmin.t.sol @@ -5,47 +5,73 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; import { SimpleStorage } from "test/universal/Proxy.t.sol"; -// Contracts -import { Proxy } from "src/universal/Proxy.sol"; -import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; -import { AddressManager } from "src/legacy/AddressManager.sol"; -import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; -import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; - // Interfaces -import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; +import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; +import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; +import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; + +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract ProxyAdmin_Test is Test { address alice = address(64); - Proxy proxy; - L1ChugSplashProxy chugsplash; - ResolvedDelegateProxy resolved; + IProxy proxy; + IL1ChugSplashProxy chugsplash; + IResolvedDelegateProxy resolved; - AddressManager addressManager; + IAddressManager addressManager; - ProxyAdmin admin; + IProxyAdmin admin; SimpleStorage implementation; function setUp() external { // Deploy the proxy admin - admin = new ProxyAdmin(alice); + admin = IProxyAdmin( + DeployUtils.create1({ + _name: "ProxyAdmin", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (alice))) + }) + ); + // Deploy the standard proxy - proxy = new Proxy(address(admin)); + proxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (address(admin)))) + }) + ); // Deploy the legacy L1ChugSplashProxy with the admin as the owner - chugsplash = new L1ChugSplashProxy(address(admin)); + chugsplash = IL1ChugSplashProxy( + DeployUtils.create1({ + _name: "L1ChugSplashProxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ChugSplashProxy.__constructor__, (address(admin)))) + }) + ); // Deploy the legacy AddressManager - addressManager = new AddressManager(); + addressManager = IAddressManager( + DeployUtils.create1({ + _name: "AddressManager", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) + }) + ); // The proxy admin must be the new owner of the address manager addressManager.transferOwnership(address(admin)); // Deploy a legacy ResolvedDelegateProxy with the name `a`. // Whatever `a` is set to in AddressManager will be the address // that is used for the implementation. - resolved = new ResolvedDelegateProxy(addressManager, "a"); - + resolved = IResolvedDelegateProxy( + DeployUtils.create1({ + _name: "ResolvedDelegateProxy", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IResolvedDelegateProxy.__constructor__, (addressManager, "a")) + ) + }) + ); // Impersonate alice for setting up the admin. vm.startPrank(alice); // Set the address of the address manager in the admin so that it @@ -57,9 +83,9 @@ contract ProxyAdmin_Test is Test { admin.setImplementationName(address(resolved), "a"); // Set the proxy types - admin.setProxyType(address(proxy), ProxyAdmin.ProxyType.ERC1967); - admin.setProxyType(address(chugsplash), ProxyAdmin.ProxyType.CHUGSPLASH); - admin.setProxyType(address(resolved), ProxyAdmin.ProxyType.RESOLVED); + admin.setProxyType(address(proxy), IProxyAdmin.ProxyType.ERC1967); + admin.setProxyType(address(chugsplash), IProxyAdmin.ProxyType.CHUGSPLASH); + admin.setProxyType(address(resolved), IProxyAdmin.ProxyType.RESOLVED); vm.stopPrank(); implementation = new SimpleStorage(); @@ -83,7 +109,7 @@ contract ProxyAdmin_Test is Test { function test_setProxyType_notOwner_reverts() external { vm.expectRevert("Ownable: caller is not the owner"); - admin.setProxyType(address(0), ProxyAdmin.ProxyType.CHUGSPLASH); + admin.setProxyType(address(0), IProxyAdmin.ProxyType.CHUGSPLASH); } function test_owner_succeeds() external view { @@ -91,9 +117,9 @@ contract ProxyAdmin_Test is Test { } function test_proxyType_succeeds() external view { - assertEq(uint256(admin.proxyType(address(proxy))), uint256(ProxyAdmin.ProxyType.ERC1967)); - assertEq(uint256(admin.proxyType(address(chugsplash))), uint256(ProxyAdmin.ProxyType.CHUGSPLASH)); - assertEq(uint256(admin.proxyType(address(resolved))), uint256(ProxyAdmin.ProxyType.RESOLVED)); + assertEq(uint256(admin.proxyType(address(proxy))), uint256(IProxyAdmin.ProxyType.ERC1967)); + assertEq(uint256(admin.proxyType(address(chugsplash))), uint256(IProxyAdmin.ProxyType.CHUGSPLASH)); + assertEq(uint256(admin.proxyType(address(resolved))), uint256(IProxyAdmin.ProxyType.RESOLVED)); } function test_erc1967GetProxyImplementation_succeeds() external { @@ -153,7 +179,7 @@ contract ProxyAdmin_Test is Test { } function changeProxyAdmin(address payable _proxy) internal { - ProxyAdmin.ProxyType proxyType = admin.proxyType(address(_proxy)); + IProxyAdmin.ProxyType proxyType = admin.proxyType(address(_proxy)); vm.prank(alice); admin.changeProxyAdmin(_proxy, address(128)); @@ -162,13 +188,13 @@ contract ProxyAdmin_Test is Test { // no longer call the proxy interface except for // the ResolvedDelegate type on which anybody can // call the admin interface. - if (proxyType == ProxyAdmin.ProxyType.ERC1967) { + if (proxyType == IProxyAdmin.ProxyType.ERC1967) { vm.expectRevert("Proxy: implementation not initialized"); admin.getProxyAdmin(_proxy); - } else if (proxyType == ProxyAdmin.ProxyType.CHUGSPLASH) { + } else if (proxyType == IProxyAdmin.ProxyType.CHUGSPLASH) { vm.expectRevert("L1ChugSplashProxy: implementation is not set yet"); admin.getProxyAdmin(_proxy); - } else if (proxyType == ProxyAdmin.ProxyType.RESOLVED) { + } else if (proxyType == IProxyAdmin.ProxyType.RESOLVED) { // Just an empty block to show that all cases are covered } else { vm.expectRevert("ProxyAdmin: unknown proxy type"); @@ -177,11 +203,11 @@ contract ProxyAdmin_Test is Test { // Call the proxy contract directly to get the admin. // Different proxy types have different interfaces. vm.prank(address(128)); - if (proxyType == ProxyAdmin.ProxyType.ERC1967) { - assertEq(Proxy(payable(_proxy)).admin(), address(128)); - } else if (proxyType == ProxyAdmin.ProxyType.CHUGSPLASH) { - assertEq(L1ChugSplashProxy(payable(_proxy)).getOwner(), address(128)); - } else if (proxyType == ProxyAdmin.ProxyType.RESOLVED) { + if (proxyType == IProxyAdmin.ProxyType.ERC1967) { + assertEq(IProxy(payable(_proxy)).admin(), address(128)); + } else if (proxyType == IProxyAdmin.ProxyType.CHUGSPLASH) { + assertEq(IL1ChugSplashProxy(payable(_proxy)).getOwner(), address(128)); + } else if (proxyType == IProxyAdmin.ProxyType.RESOLVED) { assertEq(addressManager.owner(), address(128)); } else { assert(false); @@ -222,7 +248,7 @@ contract ProxyAdmin_Test is Test { function upgradeAndCall(address payable _proxy) internal { vm.prank(alice); - admin.upgradeAndCall(_proxy, address(implementation), abi.encodeWithSelector(SimpleStorage.set.selector, 1, 1)); + admin.upgradeAndCall(_proxy, address(implementation), abi.encodeCall(SimpleStorage.set, (1, 1))); address impl = admin.getProxyImplementation(_proxy); assertEq(impl, address(implementation)); diff --git a/packages/contracts-bedrock/test/universal/SafeSend.t.sol b/packages/contracts-bedrock/test/universal/SafeSend.t.sol new file mode 100644 index 0000000000000..9b2f930fd1341 --- /dev/null +++ b/packages/contracts-bedrock/test/universal/SafeSend.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { SafeSend } from "src/universal/SafeSend.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; + +contract SafeSendTest is CommonTest { + /// @notice Tests that sending to an EOA succeeds. + function test_send_toEOA_succeeds() public { + assertNotEq(alice, address(0)); + assertNotEq(bob, address(0)); + assertEq(bob.code.length, 0); + + vm.deal(alice, 100 ether); + + uint256 aliceBalanceBefore = alice.balance; + uint256 bobBalanceBefore = bob.balance; + + vm.prank(alice); + SafeSend safeSend = new SafeSend{ value: 100 ether }(payable(bob)); + + assertEq(address(safeSend).code.length, 0); + assertEq(address(safeSend).balance, 0); + assertEq(alice.balance, aliceBalanceBefore - 100 ether); + assertEq(bob.balance, bobBalanceBefore + 100 ether); + } + + /// @notice Tests that sending to a contract succeeds without executing the + /// contract's code. + function test_send_toContract_succeeds() public { + // etch reverting code into bob + vm.etch(bob, hex"fe"); + vm.deal(alice, 100 ether); + + uint256 aliceBalanceBefore = alice.balance; + uint256 bobBalanceBefore = bob.balance; + + vm.prank(alice); + SafeSend safeSend = new SafeSend{ value: 100 ether }(payable(bob)); + + assertEq(address(safeSend).code.length, 0); + assertEq(address(safeSend).balance, 0); + assertEq(alice.balance, aliceBalanceBefore - 100 ether); + assertEq(bob.balance, bobBalanceBefore + 100 ether); + } +} diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 5ffb38fd572ee..d8c48849875d5 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -12,12 +12,13 @@ import { ForgeArtifacts, Abi, AbiEntry } from "scripts/libraries/ForgeArtifacts. import { OPContractsManager } from "src/L1/OPContractsManager.sol"; // Interfaces -import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; -import { IOptimismPortalInterop } from "src/L1/interfaces/IOptimismPortalInterop.sol"; -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; -import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; +import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; +import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; /// @title Specification_Test /// @dev Specifies common security properties of entrypoints to L1 contracts, including authorization and @@ -106,14 +107,6 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "DataAvailabilityChallenge", _sel: IDataAvailabilityChallenge.resolve.selector }); _addSpec({ _name: "DataAvailabilityChallenge", _sel: IDataAvailabilityChallenge.unlockBond.selector }); - // DelayedVetoable - _addSpec({ _name: "DelayedVetoable", _sel: _getSel("delay()") }); - _addSpec({ _name: "DelayedVetoable", _sel: _getSel("initiator()") }); - _addSpec({ _name: "DelayedVetoable", _sel: _getSel("queuedAt(bytes32)") }); - _addSpec({ _name: "DelayedVetoable", _sel: _getSel("target()") }); - _addSpec({ _name: "DelayedVetoable", _sel: _getSel("version()") }); - _addSpec({ _name: "DelayedVetoable", _sel: _getSel("vetoer()") }); - // L1CrossDomainMessenger _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("MESSAGE_VERSION()") }); _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("MIN_GAS_CALLDATA_OVERHEAD()") }); @@ -430,6 +423,8 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: _getSel("VERSION()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("batcherHash()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("gasLimit()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("eip1559Denominator()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("eip1559Elasticity()") }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.initialize.selector }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.minimumGasLimit.selector }); _addSpec({ _name: "SystemConfig", _sel: _getSel("overhead()") }); @@ -440,6 +435,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setBatcherHash.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setGasConfig.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setGasLimit.selector, _auth: Role.SYSTEMCONFIGOWNER }); + _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setEIP1559Params.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setUnsafeBlockSigner.selector, @@ -482,31 +478,39 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("VERSION()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("batcherHash()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasLimit()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Denominator()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Elasticity()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.initialize.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfig.initialize.selector }); - _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfig.minimumGasLimit.selector }); + _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.minimumGasLimit.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("overhead()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("owner()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("renounceOwnership()"), _auth: Role.SYSTEMCONFIGOWNER }); - _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfig.resourceConfig.selector }); + _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.resourceConfig.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("scalar()") }); _addSpec({ _name: "SystemConfigInterop", - _sel: ISystemConfig.setBatcherHash.selector, + _sel: ISystemConfigInterop.setBatcherHash.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfigInterop", - _sel: ISystemConfig.setGasConfig.selector, + _sel: ISystemConfigInterop.setGasConfig.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfigInterop", - _sel: ISystemConfig.setGasLimit.selector, + _sel: ISystemConfigInterop.setGasLimit.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfigInterop", - _sel: ISystemConfig.setUnsafeBlockSigner.selector, + _sel: ISystemConfigInterop.setEIP1559Params.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ + _name: "SystemConfigInterop", + _sel: ISystemConfigInterop.setUnsafeBlockSigner.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ @@ -514,7 +518,7 @@ contract Specification_Test is CommonTest { _sel: _getSel("transferOwnership(address)"), _auth: Role.SYSTEMCONFIGOWNER }); - _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfig.unsafeBlockSigner.selector }); + _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.unsafeBlockSigner.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("version()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("l1CrossDomainMessenger()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("l1ERC721Bridge()") }); @@ -550,12 +554,6 @@ contract Specification_Test is CommonTest { _auth: Role.DEPENDENCYMANAGER }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("dependencyManager()") }); - _addSpec({ - _name: "SystemConfigInterop", - _sel: _getSel( - "initialize(address,uint32,uint32,bytes32,uint64,address,(uint32,uint8,uint8,uint32,uint32,uint128),address,(address,address,address,address,address,address,address),address)" - ) - }); // ProxyAdmin _addSpec({ _name: "ProxyAdmin", _sel: _getSel("addressManager()") }); @@ -839,27 +837,25 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OPContractsManager", _sel: _getSel("version()") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("superchainConfig()") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("protocolVersions()") }); - _addSpec({ _name: "OPContractsManager", _sel: _getSel("latestRelease()") }); - _addSpec({ _name: "OPContractsManager", _sel: _getSel("implementations(string,string)") }); + _addSpec({ _name: "OPContractsManager", _sel: _getSel("l1ContractsRelease()") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("systemConfigs(uint256)") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("OUTPUT_VERSION()") }); - _addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.initialize.selector }); _addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.deploy.selector }); _addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.blueprints.selector }); _addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector }); + _addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.implementations.selector }); // OPContractsManagerInterop _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("version()") }); _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("superchainConfig()") }); _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("protocolVersions()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("latestRelease()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("implementations(string,string)") }); + _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("l1ContractsRelease()") }); _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("systemConfigs(uint256)") }); _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("OUTPUT_VERSION()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.initialize.selector }); _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.deploy.selector }); _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.blueprints.selector }); _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector }); + _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.implementations.selector }); // DeputyGuardianModule _addSpec({ @@ -942,13 +938,14 @@ contract Specification_Test is CommonTest { } /// @notice Ensures that there's an auth spec for every L1 contract function. - function testContractAuth() public { - string[] memory pathExcludes = new string[](5); - pathExcludes[0] = "src/dispute/interfaces/*"; + function test_contractAuth_works() public { + string[] memory pathExcludes = new string[](6); + pathExcludes[0] = "interfaces/dispute/*"; pathExcludes[1] = "src/dispute/lib/*"; pathExcludes[2] = "src/safe/SafeSigners.sol"; - pathExcludes[3] = "src/L1/interfaces/*"; - pathExcludes[4] = "src/governance/interfaces/*"; + pathExcludes[3] = "interfaces/L1/*"; + pathExcludes[4] = "interfaces/governance/*"; + pathExcludes[5] = "interfaces/safe/*"; Abi[] memory abis = ForgeArtifacts.getContractFunctionAbis( "src/{L1,dispute,governance,safe,universal/ProxyAdmin.sol,universal/WETH98.sol}", pathExcludes ); @@ -991,7 +988,7 @@ contract Specification_Test is CommonTest { } /// @notice Ensures that the DeputyGuardian is authorized to take all Guardian actions. - function testDeputyGuardianAuth() public view { + function test_deputyGuardianAuth_works() public view { assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length); assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, 5); diff --git a/packages/contracts-bedrock/test/universal/StandardBridge.t.sol b/packages/contracts-bedrock/test/universal/StandardBridge.t.sol index e2f62b32b8433..be7f8a51107c7 100644 --- a/packages/contracts-bedrock/test/universal/StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/universal/StandardBridge.t.sol @@ -30,7 +30,7 @@ contract StandardBridgeTester is StandardBridge { /// @title LegacyMintable /// @notice Simple implementation of the legacy OptimismMintableERC20. -contract LegacyMintable is ERC20, ILegacyMintableERC20 { +contract LegacyMintable is ERC20 { constructor(string memory _name, string memory _ticker) ERC20(_name, _ticker) { } function l1Token() external pure returns (address) { diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index eaa0c420915a5..2e7b64feb443d 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -2,32 +2,30 @@ pragma solidity 0.8.15; // Testing -import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; // Scripts -import { Executables } from "scripts/libraries/Executables.sol"; -import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; +import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; import { Process } from "scripts/libraries/Process.sol"; // Libraries import { LibString } from "@solady/utils/LibString.sol"; import { Constants } from "src/libraries/Constants.sol"; -import "src/dispute/lib/Types.sol"; -import "scripts/deploy/Deployer.sol"; +import { GameType } from "src/dispute/lib/Types.sol"; // Interfaces -import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; -import { ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; -import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; /// @title Initializer_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than /// once. This contract inherits from `ERC721Bridge_Initializer` because it is the /// deepest contract in the inheritance chain for setting up the system contracts. /// For each L1 contract both the implementation and the proxy are tested. -contract Initializer_Test is Bridge_Initializer { +contract Initializer_Test is CommonTest { /// @notice Contains the address of an `Initializable` contract and the calldata /// used to initialize it. struct InitializeableContract { @@ -46,7 +44,8 @@ contract Initializer_Test is Bridge_Initializer { function setUp() public override { super.enableAltDA(); - // Run the `Bridge_Initializer`'s `setUp()` function. + super.enableLegacyContracts(); + super.enableSoulGasToken(); super.setUp(); // Initialize the `contracts` array with the addresses of the contracts to test, the @@ -268,6 +267,14 @@ contract Initializer_Test is Bridge_Initializer { initCalldata: abi.encodeCall(l2CrossDomainMessenger.initialize, (l1CrossDomainMessenger)) }) ); + // SoulGasToken + contracts.push( + InitializeableContract({ + name: "SoulGasToken", + target: address(soulGasToken), + initCalldata: abi.encodeCall(soulGasToken.initialize, ("SoulGasToken", "SGT", address(0))) + }) + ); // L1StandardBridgeImpl contracts.push( InitializeableContract({ @@ -393,7 +400,7 @@ contract Initializer_Test is Bridge_Initializer { /// 3. The `initialize()` function of each contract cannot be called again. function test_cannotReinitialize_succeeds() public { // Collect exclusions. - string[] memory excludes = new string[](8); + string[] memory excludes = new string[](9); // TODO: Neither of these contracts are labeled properly in the deployment script. Both are // currently being labeled as their non-interop versions. Remove these exclusions once // the deployment script is fixed. @@ -412,6 +419,8 @@ contract Initializer_Test is Bridge_Initializer { // TODO: Eventually remove this exclusion. Same reason as above dispute contracts. excludes[6] = "src/L1/OPContractsManager.sol"; excludes[7] = "src/L1/OPContractsManagerInterop.sol"; + // The L2OutputOracle is not always deployed (and is no longer being modified) + excludes[8] = "src/L1/L2OutputOracle.sol"; // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes); @@ -430,21 +439,14 @@ contract Initializer_Test is Bridge_Initializer { } // Construct the query for the initialize function in the contract's ABI. - string[] memory command = new string[](3); - command[0] = Executables.bash; - command[1] = "-c"; - command[2] = string.concat( - Executables.echo, - " '", + string memory cmd = string.concat( + "echo '", ForgeArtifacts.getAbi(contractName), - "'", - " | ", - Executables.jq, - " '.[] | select(.name == \"initialize\" and .type == \"function\")'" + "' | jq '.[] | select(.name == \"initialize\" and .type == \"function\")'" ); // If the contract does not have an `initialize()` function, skip it. - if (Process.run(command).length == 0) { + if (bytes(Process.bash(cmd)).length == 0) { continue; } @@ -471,19 +473,9 @@ contract Initializer_Test is Bridge_Initializer { InitializeableContract memory _contract = contracts[i]; string memory name = _getRealContractName(_contract.name); - // Grab the value of the "initialized" storage slot. Must handle special case for the - // FaultDisputeGame and PermissionedDisputeGame contracts since these have a different - // name for the "initialized" storage slot and are currently not properly labeled in - // the deployment script. - // TODO: Update deployment script to properly label the dispute game contracts. + // Grab the value of the "initialized" storage slot. uint8 initializedSlotVal; - if (LibString.eq(name, "FaultDisputeGame") || LibString.eq(name, "PermissionedDisputeGame")) { - StorageSlot memory slot = ForgeArtifacts.getInitializedSlot(name); - bytes32 slotVal = vm.load(_contract.target, bytes32(vm.parseUint(slot.slot))); - initializedSlotVal = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF); - } else { - initializedSlotVal = deploy.loadInitializedSlot(name); - } + initializedSlotVal = deploy.loadInitializedSlot(name); // Assert that the contract is already initialized. assertTrue( diff --git a/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol b/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol index 0820f987414a2..51c2fce266786 100644 --- a/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol +++ b/packages/contracts-bedrock/test/vendor/InitializableOZv5.t.sol @@ -2,12 +2,13 @@ pragma solidity 0.8.25; import { Test } from "forge-std/Test.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { IOptimismSuperchainERC20 } from "interfaces/L2/IOptimismSuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; - +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; /// @title InitializerOZv5_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than /// once. Tests the contracts inheriting from `Initializable` from OpenZeppelin Contracts v5. + contract InitializerOZv5_Test is Test { /// @notice The storage slot of the `initialized` flag in the `Initializable` contract from OZ v5. /// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) @@ -31,8 +32,13 @@ contract InitializerOZv5_Test is Test { // OptimismSuperchainERC20 contracts.push( InitializeableContract({ - target: address(new OptimismSuperchainERC20()), - initCalldata: abi.encodeCall(OptimismSuperchainERC20.initialize, (address(0), "", "", 18)) + target: address( + DeployUtils.create1({ + _name: "OptimismSuperchainERC20", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismSuperchainERC20.__constructor__, ())) + }) + ), + initCalldata: abi.encodeCall(IOptimismSuperchainERC20.initialize, (address(0), "", "", 18)) }) ); } diff --git a/proxyd/README.md b/proxyd/README.md deleted file mode 100644 index f44b815ab2dce..0000000000000 --- a/proxyd/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# ⚠️ Important -This project has been moved to [ethereum-optimism/infra](https://github.com/ethereum-optimism/infra) diff --git a/specs/README.md b/specs/README.md deleted file mode 100644 index e75cc2b9f6686..0000000000000 --- a/specs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Specs Have Moved - -Specs have moved to a [dedicated repository](https://github.com/ethereum-optimism/specs). \ No newline at end of file diff --git a/versions.json b/versions.json deleted file mode 100644 index 423c85b51d1da..0000000000000 --- a/versions.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "go": "1.22.6", - "abigen": "v1.10.25", - "foundry": "143abd6a768eeb52a5785240b763d72a56987b4a", - "geth": "v1.14.7", - "geth_release": "1.14.7-aa55f5ea", - "eth2_testnet_genesis": "v0.10.0", - "nvm": "v20.9.0", - "slither": "0.10.2", - "kontrol": "0.1.316", - "just": "1.34.0", - "binary_signer": "1.0.4", - "semgrep": "1.90.0" -}